区块链研究实验室|使用Python创建智能合约区块链后端服务器应用

  • 时间:
  • 浏览:48
  • 来源:区块链技术网

原文作者:Jeff Scott

全堆栈开发人员习惯于同时开发后端和前端。使用区块链作为后端的问题是,它们的设置通常很复杂,与之交互更复杂。这会减慢存储和用户交互之间的简单交互。

dApp(去中心化应用程序)是与区块链交互的应用程序。目前最常见的是具有以下基本交互功能的web应用:

发送事务

获取事务状态

从智能合约获取当前状态

在开发过程中,最好简化此过程,这样我就不必每次都等待区块链处理我的交易了。我只想获取一些状态来渲染路由,以便使布局混乱。我还想在不模拟数据的情况下设计用户与区块链在不同路由上的交互。

在本文中,我将设置一个简单的Python网络服务器,该服务器将处理模拟交易到我的智能合约。这将使我能够使用现代的RESTful API模式轻松地为智能合约创建前端。

我们需要什么?

Lamden的主节点已经通过API向公共区块链提供服务。如果我们希望我们的签约服务器模仿区块链,这是一个很好的起点。

masternode API有许多与块,随机数和事务哈希有关的路由。但是我们的订约网络服务器不会花费很多精力或达成共识,因此我们不需要任何这些。

我们只关心让我们查看和更改状态的端点,因此我将列表与这五个配对。

POST /

提交交易记录

GET /ping

确保我们的服务器正在响应

GET /contracts

查看我们区块链上所有合约的名称

GET /contracts/<contract>

查看有关我们区块链上智能合约的特定信息:

Code

Methods

State Variables

GET /contracts/<contract>/<variable>

获取变量的当前状态

设置

接下来我们需要安装Sanic服务器,它将是我们的Python web服务器。

1pip3installsanic

创建服务器

创建一个新的服务器目录和一个名为contracting_server.py的文件。

首先复制sanic快速启动代码,并确保我们可以启动简单的服务器。

将此粘贴到contracting_server.py。

1#server/contracting_server.py 2fromsanicimportSanic,response 3 4app=Sanic("contractingserver") 5 6@app.route("/ping") 7asyncdefping(request): 8returnresponse.json({'status':'online'}) 910if__name__=="__main__":11app.run(host="0.0.0.0",port=3737)

然后从项目目录运行

1python3test/contracting_server.py

您应该得到这个输出,指示您的web服务器处于活动状态,并监听端口3737。

1[2020-06-0111:37:30-0400][8137][INFO]Goin'Fast@http://0.0.0.0:37372[2020-06-0111:37:30-0400][8137][INFO]Startingworker[8137]

我们还要测试一下我们的API的ping端点是否正常运行。打开您的Internet浏览器,然后导航到http://localhost:3737 / ping。您将获得一个JSON状态对象,指示服务器在线!

完美,我们现在可以添加一些端点来返回有关我们的合约环境的信息。

GET /contracts

该端点将返回当前提交给订约客户的所有合约。

默认情况下,合同包仅包含1个合约(提交合约)。因此我们需要添加我们的合约来了解合约。通过添加签约客户并调用前一教程的测试中的方法来做到这一点。

1#server/contracting_server.py 2fromsanicimportSanic,response 3 4fromcontracting.clientimportContractingClient 5client=ContractingClient() 6 7withopen('my_token.py')asf: 8code=f.read() 9client.submit(code,name='my_token')1011app=Sanic("contractingserver")1213@app.route("/ping")14...

现在,当智能合约服务器启动时,它将默认添加我们的智能合约。

client.get_contracts()将返回加载到签约客户端中的所有智能合约的列表。

通过添加以下代码来创建端点,以提供调用client.get_contracts()的结果。

1#server/contracting_server.py 2... 3returnresponse.json({'status':'online'}) 4 5#GetallContractsinState(listofnames) 6@app.route("/contracts") 7asyncdefget_contracts(request): 8contracts=client.get_contracts() 9returnresponse.json({'contracts':contracts})1011if__name__=="__main__":12...

通过打开终端来重新启动Web服务器,通过按Control + C来停止服务器,并使用python3 tests/contracting_server.py启动它。从现在开始,本教程将简称为“重启Web服务器”。

如果您在这里遇到错误,请检查mongodb是否已安装并启动。

浏览到我们的新端点http://localhost:3737/contracts,您现在将在网络上看到我们的my_token智能合约。

GET /contracts/<contract>

我们可以使用此端点获取有关合约的元数据。这对于调试我们自己的智能合约或显示有关智能合约的动态信息很有用。

我们需要ast包来遍历代码,因此请在contracting_server.py顶部添加该导入。

1#server/contracting_server.py2fromsanicimportSanic,response3importast45fromcontracting.db.encoderimportencode6fromcontracting.clientimportContractingClient7...

现在添加此新路由的代码并重新启动服务器。

1#server/contracting_server.py 2... 3returnresponse.json({'contracts':contracts}) 4 5@app.route("/contracts/<contract>") 6#Getthesourcecodeofaspecificcontract 7asyncdefget_contract(request,contract): 8#Usetheclientraw_drivertogetthecontractcodefromthedb 9contract_code=client.raw_driver.get_contract(contract)1011funcs=[]12variables=[]13hashes=[]1415#Parsethecodeintoawalkabletree16tree=ast.parse(contract_code)1718#Parseoutallfunctions19function_defs=[nforninast.walk(tree)ifisinstance(n,ast.FunctionDef)]20fordefinitioninfunction_defs:21func_name=definition.name22kwargs=[arg.argforargindefinition.args.args]2324funcs.append({'name':func_name,'arguments':kwargs})2526#ParseoutalldefinedstateVariablesandHashes27assigns=[nforninast.walk(tree)ifisinstance(n,ast.Assign)]28forassigninassigns:29iftype(assign.value)==ast.Call:30ifassign.value.func.id=='Variable':31variables.append(assign.targets[0].id.lstrip('__'))32elifassign.value.func.id=='Hash':33hashes.append(assign.targets[0].id.lstrip('__'))3435#ReturnallInformation36returnresponse.json({37'name':contract,38'code':contract_code,39'methods':funcs,40'variables':variables,41'hashes':hashes42},status=200)4344if__name__=="__main__":45...

浏览到我们的新端点http://localhost:3737/contracts/my_token,您将看到有关我们智能合约的所有元数据。

让我们来看看!

第一个方法“seed”是我们的构造函数方法。智能合约在提交后对其进行了重命名,因此无法再次调用它。

第二种方法是我们的传输方法,我们可以看到它需要2个参数,AMOUNT和RECEIVER。

然后我们看到我们的智能合约没有状态变量,但是它确实有一个状态哈希,称为“ S”。

GET /contracts/<variable>

能够查询S哈希的状态以获取我们的余额会很有帮助。我们已经在测试文件中做到了这一点,因此让它动态化并从新端点提供它,以便以后可用于我们的网络应用程序。

我们需要从收缩到正确格式化我们的值进行传输的encode函数。

将该导入语句添加到contracting_server.py的顶部。

1#server/contracting_server.py23fromsanicimportSanic,response4importast56fromcontracting.db.encoderimportencode7fromcontracting.clientimportContractingClient8client=ContractingClient()9...

现在添加此新路由的代码,然后重新启动服务器。

1#server/contracting_server.py 2... 3#Returnthecurrentstateofavariable 4@app.route("/contracts/<contract>/<variable>") 5asyncdefget_variable(request,contract,variable): 6#Checkifcontractexists.Ifnot,returnerror 7contract_code=client.raw_driver.get_contract(contract) 8ifcontract_codeisNone: 9returnresponse.json({'error':'{}doesnotexist'.format(contract)},status=404)10#Parsekeyfromrequestobject11key=request.args.get('key')12ifkeyisnotNone:13key=key.split(',')1415#Createthekeycontractingwillusetogetthevalue16k=client.raw_driver.make_key(contract=contract,variable=variable,args=key)1718#Getvalue19value=client.raw_driver.get(k)2021#Ifthevariableorthevaluedidn'texistsreturnNone22ifvalueisNone:23returnresponse.json({'value':None},status=404)2425#Iftherewasavalue,returnitformatted26returnresponse.json({'value':value},status=200,dumps=encode)27...

这个端点需要比以前的端点更多的信息。

variable:要查询的状态变量(单个或哈希)

key:查询时提供哈希的key

为了提供此信息,我们将URL参数化

S-存储状态的变量

key-要为其分配值的密钥的名称

1localhost:3737/contracts/my_token/S?key=me

浏览到我们的新端点http://localhost:3737/contracts/my_token/S?key=me以获取“ me”的余额。

更改为不存在的密钥。如“ you”,将返回空值,因为它不存在于状态中。

POST /

到目前为止,我们的API正在形成相当好的状态。现在我们需要做的就是接受一个交易进入我们的网络。

在真正的区块链上,交易结果不是即时的,因为它需要排队才能被节点处理。当您提交交易时,区块链将回复一个收据(称为交易哈希),然后您可以使用它来查找交易的成功。

我们的合约网络服务器不需要做任何这些。我们不需要区块或共识,所以我们可以简单地写我们的价值陈述。这有助于快速发展。

添加此新路由的代码并重新启动服务器。

1#server/contracting_server.py 2... 3#Iftherewasavalue,returnitformatted 4returnresponse.json({'value':value},status=200,dumps=encode) 5@app.route("/",methods=["POST",]) 6asyncdefsubmit_transaction(request): 7#Gettransactiondetails 8contract_name=request.json.get('contract') 9method_name=request.json.get('method')10kwargs=request.json.get('args')11sender=request.json.get('sender')1213#Setthesender14client.signer=sender1516#Getreferencetocontract17contract=client.get_contract(contract_name)1819#Returnerrorofcontractdoesnotexist20ifcontract_nameisNone:21returnresponse.json({'error':'{}doesnotexist'.format(contract)},status=404)2223#Getreferencetothecontractmethodtobecalled24method=getattr(contract,method_name)2526#Callmethodwithsuppliedargumentsandreturnstatus27try:28method(**kwargs)29returnresponse.json({'status':0})30exceptExceptionaserr:31returnresponse.json({'status':1,'error':err})32if__name__=="__main__":33...

要达到此端点,我们需要创建一个http POST请求。最简单的方法是使用如下所示的curl语句。调用我们的方法需要以下信息:

1. sender: 发送交易的人的身份。这将成为我们智能合约中的ctx.caller。

2. contract:我们要定位的智能合同的名称。

3. method: 我们要定位的智能合约的名称。

4. args:传递给方法的参数。

1curl-XPOST-d'{2"sender":"me",3"contract":"my_token",4"method":"transfer",5"args":"{\"amount\":10,\"receiver\":\"you\"}"6}'-v-i'http://localhost:3737/'

当然您可以使用选择的任何程序来测试API(例如Postman或Paw)。只需使用正文JSON并将请求发送到http:// localhost:3737/,您就应该返回此响应。

1{2“status”:03}

如果尝试发送太多令牌,则会返回断言错误。

1{2"status":1,3"error":"Transferamountexceedsavailabletokenbalance"4}

然后我们可以使用之前制作的变量端点来检查状态是否已更改

这是我们刚刚创建的整个开发服务器代码。

1#server/contracting_server.py 2 3fromsanicimportSanic,response 4importast 5 6fromcontracting.db.encoderimportencode 7fromcontracting.clientimportContractingClient 8client=ContractingClient() 9 10withopen('my_token.py')asf: 11code=f.read() 12client.submit(code,name='my_token') 13 14app=Sanic("contractingserver") 15 16#Makesuretheserverisonline 17@app.route("/ping") 18asyncdefping(request): 19returnresponse.json({'status':'online'}) 20 21#GetallContractsinState(listofnames) 22@app.route("/contracts") 23asyncdefget_contracts(request): 24contracts=client.get_contracts() 25returnresponse.json({'contracts':contracts}) 26 27@app.route("/contracts/<contract>") 28#Getthesourcecodeofaspecificcontract 29asyncdefget_contract(request,contract): 30#Usetheclientraw_drivertogetthecontractcodefromthedb 31contract_code=client.raw_driver.get_contract(contract) 32 33#Returnanerrorresponseifthecodedoesnotexist 34ifcontract_codeisNone: 35returnresponse.json({'error':'{}doesnotexist'.format(contract)},status=404) 36 37funcs=[] 38variables=[] 39hashes=[] 40 41#Parsethecodeintoawalkabletree 42tree=ast.parse(contract_code) 43 44#Parseoutallfunctions 45function_defs=[nforninast.walk(tree)ifisinstance(n,ast.FunctionDef)] 46fordefinitioninfunction_defs: 47func_name=definition.name 48kwargs=[arg.argforargindefinition.args.args] 49 50funcs.append({'name':func_name,'arguments':kwargs}) 51 52#ParseoutalldefinedstateVariablesandHashes 53assigns=[nforninast.walk(tree)ifisinstance(n,ast.Assign)] 54forassigninassigns: 55iftype(assign.value)==ast.Call: 56ifassign.value.func.id=='Variable': 57variables.append(assign.targets[0].id.lstrip('__')) 58elifassign.value.func.id=='Hash': 59hashes.append(assign.targets[0].id.lstrip('__')) 60 61#ReturnallInformation 62returnresponse.json({ 63'name':contract, 64'code':contract_code, 65'methods':funcs, 66'variables':variables, 67'hashes':hashes 68},status=200) 69 70#Returnthecurrentstateofavariable 71@app.route("/contracts/<contract>/<variable>") 72asyncdefget_variable(request,contract,variable): 73#Checkifcontractexists.Ifnot,returnerror 74contract_code=client.raw_driver.get_contract(contract) 75ifcontract_codeisNone: 76returnresponse.json({'error':'{}doesnotexist'.format(contract)},status=404) 77#Parsekeyfromrequestobject 78key=request.args.get('key') 79ifkeyisnotNone: 80key=key.split(',') 81 82#Createthekeycontractingwillusetogetthevalue 83k=client.raw_driver.make_key(contract=contract,variable=variable,args=key) 84 85#Getvalue 86value=client.raw_driver.get(k) 87 88#Ifthevariableorthevaluedidn'texistsreturnNone 89ifvalueisNone: 90returnresponse.json({'value':None},status=404) 91 92#Iftherewasavalue,returnitformatted 93returnresponse.json({'value':value},status=200,dumps=encode) 94 95@app.route("/",methods=["POST",]) 96asyncdefsubmit_transaction(request): 97#Gettransactiondetails 98contract_name=request.json.get('contract') 99method_name=request.json.get('method')100kwargs=request.json.get('args')101sender=request.json.get('sender')102103#Setthesender104client.signer=sender105106#Getreferencetocontract107contract=client.get_contract(contract_name)108109#Returnerrorofcontractdoesnotexist110ifcontract_nameisNone:111returnresponse.json({'error':'{}doesnotexist'.format(contract)},status=404)112113#Getreferencetothecontractmethodtobecalled114method=getattr(contract,method_name)115116#Callmethodwithsuppliedargumentsandreturnstatus117try:118method(**kwargs)119returnresponse.json({'status':0})120exceptExceptionaserr:121returnresponse.json({'status':1,'error':str(err)})122123124if__name__=="__main__":125app.run(host="0.0.0.0",port=3737)

猜你喜欢

从零开发区块链应用(一)-golang配置文件管理工具viper

理解 HTTP 构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及 server 端的 response。所谓的 http 服务器,主要在于如何接受 client 的 request,并向 client 返回 response

2022-01-20

从零开发区块链应用(二)--mysql安装及数据库表的安装创建

varchar 类型的长度是可变的,在创建表时指定了最大长度。定义时,其最大值可以取 0~~65525 之间的任意值。指定了 varchar 类型的最大值以后,其长度可以在 o 到最大长度之间。

2022-01-20

从零开发区块链应用(三)--mysql初始化及gorm框架使用

Gorm 是 golang 的一个 orm 框架,它提供了对数据库操作的封装,使用起来相当便利。所以如果对 mysql 使用操作不熟悉,建议也可以使用 gorm 框架操作 mysql 数据库。

2022-01-20

从零开发区块链应用(四)--自定义业务错误信息

在实际业务开发中,一个条错误信息需要包含两部分内容:直接展示给用户的 message 和用于开发人员 debug 的 error 。message 可能会直接展示给用户,error 是用于 debug 的错误信息,可能包含敏感/内部信息,不宜对外展示

2022-01-20

从零开发区块链应用(五)--golang网络请求

理解 HTTP 构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及 server 端的 response。所谓的 http 服务器,主要在于如何接受 client 的 request,并向 client 返回 response。

2022-01-20