使用Python建立智慧合約區塊鏈後端伺服器應用

買賣虛擬貨幣
全堆疊開發人員習慣於同時開發後端和前端。使用區塊鏈作為後端的問題是,它們的設定通常很複雜,與之互動更復雜。這會減慢儲存和使用者互動之間的簡單互動。dApp(去中心化應用程式)是與區塊鏈互動的應用程式。目前最常見的是具有以下基本互動功能的web應用:· 傳送事務· 獲取事務狀態· 從智慧合約獲取當前狀態在開發過程中,最好簡化此過程,這樣我就不必每次都等待區塊鏈處理我的交易了。我只想獲取一些狀態來渲染路由,以便使佈局混亂。我還想在不模擬資料的情況下設計使用者與區塊鏈在不同路由上的互動。
在本文中,我將設定一個簡單的Python網路伺服器,該伺服器將處理模擬交易到我的智慧合約。這將使我能夠使用現代的RESTful API模式輕鬆地為智慧合約建立前端。我們需要什麼?Lamden的主節點已經透過API向公共區塊鏈提供服務。如果我們希望我們的簽約伺服器模仿區塊鏈,這是一個很好的起點。masternode API有許多與塊,隨機數和事務雜湊有關的路由。但是我們的訂約網路伺服器不會花費很多精力或達成共識,因此我們不需要任何這些。我們只關心讓我們檢視和更改狀態的端點,因此我將列表與這五個配對。POST /
提交交易記錄GET /ping確保我們的伺服器正在響應GET /contracts檢視我們區塊鏈上所有合約的名稱GET /contracts/<contract>
檢視有關我們區塊鏈上智慧合約的特定資訊:· Code· Methods· State VariablesGET /contracts/<contract>/<variable>獲取變數的當前狀態
設定接下來我們需要安裝Sanic伺服器,它將是我們的Python web伺服器。1pip3 install sanic建立伺服器

建立一個新的伺服器目錄和一個名為contracting_server.py的檔案。

首先複製sanic快速啟動程式碼,並確保我們可以啟動簡單的伺服器。

將此貼上到contracting_server.py。

 1# server/contracting_server.py
 2from sanic import Sanic, response
 3
 4app = Sanic("contracting server")
 5
 admin@chaindaily("/ping")
 7async def ping(request):
 8    return response.json({'status': 'online'})
 9
10if __name__ == "__main__":
11    app.run(host="0.0.0.0", port=3737)

然後從專案目錄執行

1python3 test/contracting_server.py

您應該得到這個輸出,指示您的web伺服器處於活動狀態,並監聽埠3737。

1[2020-06-01 11:37:30 -0400] [8137] [INFO] Goin' Fast @ http://0.0.0.0:3737
2[2020-06-01 11:37:30 -0400] [8137] [INFO] Starting worker [8137]

我們還要測試一下我們的API的ping端點是否正常執行。開啟您的Internet瀏覽器,然後導航到http://localhost:3737 / ping。您將獲得一個JSON狀態物件,指示伺服器線上!

完美,我們現在可以新增一些端點來返回有關我們的合約環境的資訊。

GET /contracts

該端點將返回當前提交給訂約客戶的所有合約。

預設情況下,合同包僅包含1個合約(提交合約)。因此我們需要新增我們的合約來了解合約。透過新增簽約客戶並呼叫前一教程的測試中的方法來做到這一點。

 1# server/contracting_server.py
 2from sanic import Sanic, response
 3
 4from contracting.client import ContractingClient
 5client = ContractingClient()
 6
 7with open('my_token.py') as f:
 8    code = f.read()
 9    client.submit(code, name='my_token')
10
11app = Sanic("contracting server")
12
admin@chaindaily("/ping")
14...

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

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

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

 1# server/contracting_server.py
 2...
 3return response.json({'status': 'online'})
 4
 5# Get all Contracts in State (list of names)
 admin@chaindaily("/contracts")
 7async def get_contracts(request):
 8    contracts = client.get_contracts()
 9    return response.json({'contracts': contracts})
10
11if __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.py
2from sanic import Sanic, response
3import ast
4
5from contracting.db.encoder import encode
6from contracting.client import ContractingClient
7...

現在新增此新路由的程式碼並重新啟動伺服器。

 1# server/contracting_server.py
 2...
 3return response.json({'contracts': contracts})
 4
 admin@chaindaily("/contracts/<contract>")
 6# Get the source code of a specific contract
 7async def get_contract(request, contract):
 8    # Use the client raw_driver to get the contract code from the db
 9    contract_code = client.raw_driver.get_contract(contract)
10
11    funcs = []
12    variables = []
13    hashes = []
14
15    # Parse the code into a walkable tree
16    tree = ast.parse(contract_code)
17
18    # Parse out all functions
19    function_defs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
20    for definition in function_defs:
21        func_name = definition.name
22        kwargs = [arg.arg for arg in definition.args.args]
23
24        funcs.append({'name': func_name, 'arguments': kwargs})
25
26    # Parse out all defined state Variables and Hashes
27    assigns = [n for n in ast.walk(tree) if isinstance(n, ast.Assign)]
28    for assign in assigns:
29        if type(assign.value) == ast.Call:
30            if assign.value.func.id == 'Variable':
31                variables.append(assign.targets[0].id.lstrip('__'))
32            elif assign.value.func.id == 'Hash':
33                hashes.append(assign.targets[0].id.lstrip('__'))
34
35    #Return all Information
36    return response.json({
37        'name': contract,
38        'code': contract_code,
39        'methods': funcs,
40        'variables': variables,
41        'hashes': hashes
42    }, status=200)
43
44if __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.py
2
3from sanic import Sanic, response
4import ast
5
6from contracting.db.encoder import encode
7from contracting.client import ContractingClient
8client = ContractingClient()
9...

現在新增此新路由的程式碼,然後重新啟動伺服器。

 1# server/contracting_server.py
 2...
 3# Return the current state of a variable
 admin@chaindaily("/contracts/<contract>/<variable>")
 5async def get_variable(request, contract, variable):
 6    # Check if contract exists. If not, return error
 7    contract_code = client.raw_driver.get_contract(contract)
 8    if contract_code is None:
 9        return response.json({'error': '{} does not exist'.format(contract)}, status=404)
10    # Parse key from request object
11    key = request.args.get('key')
12    if key is not None:
13        key = key.split(',')
14
15    # Create the key contracting will use to get the value
16    k = client.raw_driver.make_key(contract=contract, variable=variable, args=key)
17
18    # Get value
19    value = client.raw_driver.get(k)
20
21    # If the variable or the value didn't exists return None
22    if value is None:
23        return response.json({'value': None}, status=404)
24
25    # If there was a value, return it formatted
26    return response.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# If there was a value, return it formatted
 4return response.json({'value': value}, status=200, dumps=encode)
 admin@chaindaily("/", methods=["POST",])
 6async def submit_transaction(request):
 7    # Get transaction details
 8    contract_name = request.json.get('contract')
 9    method_name = request.json.get('method')
10    kwargs = request.json.get('args')
11    sender = request.json.get('sender')
12
13    # Set the sender
14    client.signer = sender
15
16    # Get reference to contract
17    contract = client.get_contract(contract_name)
18
19    # Return error of contract does not exist
20    if contract_name is None:
21        return response.json({'error': '{} does not exist'.format(contract)}, status=404)
22
23    # Get reference to the contract method to be called
24    method = getattr(contract, method_name)
25
26    # Call method with supplied arguments and return status
27    try:
28        method(**kwargs)
29        return response.json({'status': 0})
30    except Exception as err:
31        return response.json({'status': 1, 'error': err})
32if __name__ == "__main__":
33...

要達到此端點,我們需要建立一個http POST請求。最簡單的方法是使用如下所示的curl語句。呼叫我們的方法需要以下資訊:

1. sender: 傳送交易的人的身份。這將成為我們智慧合約中的ctx.caller。
2. contract:我們要定位的智慧合同的名稱。
3. method: 我們要定位的智慧合約的名稱。
4. args:傳遞給方法的引數。

1curl -X POST -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”: 0
3}

如果嘗試傳送太多令牌,則會返回斷言錯誤。

1{
2    "status": 1,
3    "error": "Transfer amount exceeds available token balance"
4}

然後我們可以使用之前製作的變數端點來檢查狀態是否已更改

這是我們剛剛建立的整個開發伺服器程式碼。

  1# server/contracting_server.py
  2
  3from sanic import Sanic, response
  4import ast
  5
  6from contracting.db.encoder import encode
  7from contracting.client import ContractingClient
  8client = ContractingClient()
  9
 10with open('my_token.py') as f:
 11    code = f.read()
 12    client.submit(code, name='my_token')
 13
 14app = Sanic("contracting server")
 15
 16# Make sure the server is online
 admin@chaindaily("/ping")
 18async def ping(request):
 19    return response.json({'status': 'online'})
 20
 21# Get all Contracts in State (list of names)
 admin@chaindaily("/contracts")
 23async def get_contracts(request):
 24    contracts = client.get_contracts()
 25    return response.json({'contracts': contracts})
 26
 admin@chaindaily("/contracts/<contract>")
 28# Get the source code of a specific contract
 29async def get_contract(request, contract):
 30    # Use the client raw_driver to get the contract code from the db
 31    contract_code = client.raw_driver.get_contract(contract)
 32
 33    # Return an error response if the code does not exist
 34    if contract_code is None:
 35        return response.json({'error': '{} does not exist'.format(contract)}, status=404)
 36
 37    funcs = []
 38    variables = []
 39    hashes = []
 40
 41    # Parse the code into a walkable tree
 42    tree = ast.parse(contract_code)
 43
 44    # Parse out all functions
 45    function_defs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
 46    for definition in function_defs:
 47        func_name = definition.name
 48        kwargs = [arg.arg for arg in definition.args.args]
 49
 50        funcs.append({'name': func_name, 'arguments': kwargs})
 51
 52    # Parse out all defined state Variables and Hashes
 53    assigns = [n for n in ast.walk(tree) if isinstance(n, ast.Assign)]
 54    for assign in assigns:
 55        if type(assign.value) == ast.Call:
 56            if assign.value.func.id == 'Variable':
 57                variables.append(assign.targets[0].id.lstrip('__'))
 58            elif assign.value.func.id == 'Hash':
 59                hashes.append(assign.targets[0].id.lstrip('__'))
 60
 61    #Return all Information
 62    return response.json({
 63        'name': contract,
 64        'code': contract_code,
 65        'methods': funcs,
 66        'variables': variables,
 67        'hashes': hashes
 68    }, status=200)
 69
 70# Return the current state of a variable
 admin@chaindaily("/contracts/<contract>/<variable>")
 72async def get_variable(request, contract, variable):
 73    # Check if contract exists. If not, return error
 74    contract_code = client.raw_driver.get_contract(contract)
 75    if contract_code is None:
 76        return response.json({'error': '{} does not exist'.format(contract)}, status=404)
 77    # Parse key from request object
 78    key = request.args.get('key')
 79    if key is not None:
 80        key = key.split(',')
 81
 82    # Create the key contracting will use to get the value
 83    k = client.raw_driver.make_key(contract=contract, variable=variable, args=key)
 84
 85    # Get value
 86    value = client.raw_driver.get(k)
 87
 88    # If the variable or the value didn't exists return None
 89    if value is None:
 90        return response.json({'value': None}, status=404)
 91
 92    # If there was a value, return it formatted
 93    return response.json({'value': value}, status=200, dumps=encode)
 94
 admin@chaindaily("/", methods=["POST",])
 96async def submit_transaction(request):
 97    # Get transaction details
 98    contract_name = request.json.get('contract')
 99    method_name = request.json.get('method')
100    kwargs = request.json.get('args')
101    sender = request.json.get('sender')
102
103    # Set the sender
104    client.signer = sender
105
106    # Get reference to contract
107    contract = client.get_contract(contract_name)
108
109    # Return error of contract does not exist
110    if contract_name is None:
111        return response.json({'error': '{} does not exist'.format(contract)}, status=404)
112
113    # Get reference to the contract method to be called
114    method = getattr(contract, method_name)
115
116    # Call method with supplied arguments and return status
117    try:
118        method(**kwargs)
119        return response.json({'status': 0})
120    except Exception as err:
121        return response.json({'status': 1, 'error': str(err)})
122
123
124if __name__ == "__main__":
125    app.run(host="0.0.0.0", port=3737)

免責聲明:

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

推荐阅读

;