區塊鏈研究實驗室|使用Python建立智慧合約區塊鏈後端伺服器應用

買賣虛擬貨幣

原文作者: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 [email protected]("/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")[email protected]("/ping")14...

現在,當智慧合約伺服器啟動時,它將預設新增我們的智慧合約。

client.get_contracts()將返回載入到簽約客戶端中的所有智慧合約的列表。

透過新增以下程式碼來建立端點,以提供呼叫client.get_contracts()的結果。

1#server/contracting_server.py 2... 3returnresponse.json({'status':'online'}) 4 5#GetallContractsinState(listofnames) [email protected]("/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 [email protected]("/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 [email protected]("/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) [email protected]("/",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 [email protected]("/ping") 18asyncdefping(request): 19returnresponse.json({'status':'online'}) 20 21#GetallContractsinState(listofnames) [email protected]("/contracts") 23asyncdefget_contracts(request): 24contracts=client.get_contracts() 25returnresponse.json({'contracts':contracts}) 26 [email protected]("/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 [email protected]("/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 [email protected]("/",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)

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;