建立一個新的伺服器目錄和一個名為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)