玩轉存證合約(二)| 利用Python開發區塊鏈后端介面
實驗環境
Python3.6+
FISCO BCOS
WeBase-Front
Centos7 / Ubuntu
專案地址:https://github.com/WeLightProject/Evidence-Sample-Python
前言
緊接上文,我們在上一文介紹了存證合約的具體內容以及存證合約的部署和呼叫,但是遇到一個問題,我們想通過這個合約來開發自己的web或者應用,該怎么辦?
FISCO BCOS區塊鏈向外部暴露了介面,外部業務程式能夠通過FISCO BCOS提供的SDK來呼叫這些介面,開發者只需要根據自身業務程式的要求,選擇相應語言的SDK,用SDK提供的API進行編程,即可實作對區塊鏈的操作,(https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/sdk/index.html)
目前,FISCO BCOS 提供的SDK包括:
- Java SDK (穩定、功能強大、無內置控制臺,推薦使用)
- Web3SDK (舊版Java SDK)
- Python SDK (簡單輕便、有內置控制臺)
- Node-js SDK (簡單輕便、有內置控制臺)
- Go SDK (簡單輕便、有內置控制臺)
- C# SDK (完整適配Json RPC API)
我們本節將利用Python-SDK,利用Python來實作區塊鏈的后端開發,
安裝Python-SDK
依賴軟體
- Ubuntu:
sudo apt install -y zlib1g-dev libffi6 libffi-dev wget git - CentOS:
sudo yum install -y zlib-devel libffi-devel wget git - MacOs:
brew install wget npm git
初始化環境(若python環境符合要求,可跳過)
Linux環境初始化
拉取源代碼
git clone https://github.com/FISCO-BCOS/python-sdk
配置環境(安裝pyenv和python,若python版本>=3.6.3可跳過本步)
# 獲取python版本
python --version
## --------若python版本小于3.6.3,執行下面流程--------------------------------------
# 判斷python版本,并為不符合條件的python環境安裝python 3.7.3的虛擬環境,命名為python-sdk
# 若python環境符合要求,可以跳過此步
# 若腳本執行出錯,請檢查是否參考[依賴軟體]說明安裝了依賴
# 提示:安裝python-3.7.3可能耗時比較久
cd python-sdk && bash init_env.sh -p
## --------若通過bash init_env.sh -p安裝了python-sdk虛擬環境,執行下面流程-------------
# 激活python-sdk虛擬環境
source ~/.bashrc && pyenv activate python-sdk && pip install --upgrade pip
安裝Python SDK依賴
cd python-sdk
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
初始化配置
# 該腳本執行操作如下:
# 1. 拷貝client_config.py.template->client_config.py
# 2. 安裝solc編譯器
bash init_env.sh -i
若MacOS環境solc安裝較慢,可在python-sdk目錄下執行如下命令安裝solcjs,python-sdk自動加載nodejs編譯器:
# 安裝編譯器
npm install solc@v0.4.25
tips:webase-front部署合約之后,會生成合約地址,bin和abi檔案,將abi檔案保存到contracts/目錄下,合約地址(contractAddress)也需要復制保存用于合約呼叫(0x8d6327bf7253e87add9d17212cc76dd7ff1d380c),這樣子才可以呼叫相應的合約,IDE頁面下方和合約串列都有對應的資訊



配置Channel通信協議
Python SDK支持使用Channel協議與FISCO BCOS節點通信,通過SSL加密通信保障SDK與節點通信的機密性,
設SDK連接的節點部署在目錄~/fisco/nodes/127.0.0.1目錄下,則通過如下步驟使用Channel協議:
配置Channel資訊
在節點目錄下的 config.ini 檔案中獲取 channel_listen_port, 這里為20200
[rpc]
listen_ip=0.0.0.0
channel_listen_port=20200
jsonrpc_listen_port=8545
切換到python-sdk目錄,修改 client_config.py 檔案中channel_host為實際的IP,channel_port為上步獲取的channel_listen_port:
channel_host = "127.0.0.1"
channel_port = 20200
配置證書
# 若節點與python-sdk位于不同機器,請將節點sdk目錄下所有相關檔案拷貝到bin目錄
# 若節點與sdk位于相同機器,直接拷貝節點證書到SDK配置目錄
cp ~/fisco/nodes/127.0.0.1/sdk/* bin/
配置證書路徑
client_config.py的channel_node_cert和channel_node_key選項分別用于配置SDK證書和私鑰release-2.1.0版本開始,SDK證書和私鑰更新為sdk.crt和sdk.key,配置證書路徑前,請先檢查上步拷貝的證書名和私鑰名,并將channel_node_cert配置為SDK證書路徑,將channel_node_key配置為SDK私鑰路徑
檢查從節點拷貝的sdk證書路徑,若sdk證書和私鑰路徑分別為bin/sdk.crt和bin/sdk.key,則client_config.py中相關配置項如下:
channel_node_cert = "bin/sdk.crt" # 采用channel協議時,需要設定sdk證書,如采用rpc協議通信,這里可以留空
channel_node_key = "bin/sdk.key" # 采用channel協議時,需要設定sdk私鑰,如采用rpc協議通信,這里可以留空
若sdk證書和私鑰路徑分別為bin/node.crt和bin/node.key,則client_config.py中相關配置項如下:
channel_node_cert = "bin/node.crt" # 采用channel協議時,需要設定sdk證書,如采用rpc協議通信,這里可以留空
channel_node_key = "bin/node.key" # 采用channel協議時,需要設定sdk私鑰,如采用rpc協議通信,這里可以留空
國密支持
- 支持國密版本的非對稱加密、簽名驗簽(SM2), HASH演算法(SM3),對稱加解密(SM4)
- 國密版本在使用上和非國密版本基本一致,主要是配置差異,
- 國密版本sdk同一套代碼可以連接國密和非國密的節點,需要根據不同的節點配置相應的IP埠和證書
- 因為當前版本的實作里,賬戶檔案格式有差異,所以國密的賬戶檔案和ECDSA的賬戶檔案采用不同的配置
連接國密節點時,有以下相關的配置項需要修改和確認,IP埠也需要確認是指向國密版本節點
crypto_type = "GM" #密碼演算法選擇: 大小寫不敏感:"GM" 標識國密, "ECDSA" 或其他是橢圓曲線默認實作,
gm_account_keyfile = "gm_account.json" #國密賬號的存盤檔案,可以加密存盤,如果留空則不加載
gm_account_password = "123456" #如果不設密碼,置為None或""則不加密
gm_solc_path = "./bin/solc/v0.4.25/solc-gm" #合約編譯器配置,通過執行bash init_env.sh -i命令下載
使用Channel協議訪問節點
# 獲取FISCO BCOS節點版本號
./console.py getNodeVersion

Event事件回呼
- 針對已經部署在鏈上某個地址的合約,先注冊要監聽的事件,當合約被交易呼叫,且生成事件時,節點可以向客戶端推送相應的事件
- 事件定義如有indexed型別的輸入,可以指定監聽某個特定值作為過濾,如事件定義為 on_set(string name,int indexed value),可以增加一個針對value的topic監聽,只監聽value=5的事件
- 具體實作參考demo_event_callback.py,使用的命令列為:
params: contractname address event_name indexed
1. contractname : 合約的檔案名,不需要帶sol后綴,默認在當前目錄的contracts目錄下
2. address : 十六進制的合約地址,或者可以為:last,表示采用bin/contract.ini里的記錄
3. event_name : 可選,如不設定監聽所有事件
4. indexed : 可選,根據event定義里的indexed欄位,作為過濾條件)
eg: for contract sample [contracts/HelloEvent.sol], use cmdline:
python demo_event_callback.py HelloEvent last
--listen all event at all indexed :
python demo_event_callback.py HelloEvent last on_set
--listen event on_set(string newname) (no indexed):
python demo_event_callback.py HelloEvent last on_number 5
--listen event on_number(string name,int indexed age), age ONLY 5 :
成功安裝Python-SDK以及保存abi和合約地址之后,就可以進行后端開發了,
構造Evidence類
現階段,Python-SDK不支持pip下載,配置好的SDK和專案應該處于同一級目錄下,例如:

首先介紹一下合約呼叫的初始化,
我們需要通過BcosClient(from client.bcosclient import BcosClient)底下的**call()函式和sendRawTransaction() / sendRawTransactionGetReceipt()**函式實作合約的呼叫,
- call() 不上鏈,不發送交易,
- sendRawTransaction() / sendRawTransactionGetReceipt() 上鏈,發送交易,
**sendRawTransaction()函式只回傳交易地址,并不會回傳交易的具體內容,如果需要獲取交易的具體內容則需要呼叫sendRawTransactionGetReceipt()**函式,(我們需要用api顯示這些內容,所以使用sendRawTransactionGetReceipt()函式)
通過觀察這幾個函式



- to_address —— 合約地址(string格式)
- contract_abi —— abi內容(json格式/dict格式)
所以我們需要提供給SDK兩個東西——abi(contracts/EvidenceFactory.abi)和合約地址(0x8d6327bf7253e87add9d17212cc76dd7ff1d380c),
Python-SDK提供了導入abi檔案的函式,直接呼叫即可(讀取abi檔案json內容)
from client.datatype_parser import DatatypeParser
abi_file = "contracts/EvidenceFactory.abi"
data_parser = DatatypeParser()
data_parser.load_abi_file(abi_file)
print(data_parser.contract_abi)

所以只要能設計一個類能實作call函式和sendRawTransactionGetReceipt()即可,
以HelloWorld.sol為例
在python-sdk檔案夾下執行python3 console.py deploy HelloWorld即可部署成功,abi會自動在contracts/目錄下生成,
from client.bcosclient import BcosClient
from client.datatype_parser import DatatypeParser
class Contract:
def __init__(self, address: str):
"""
:param address: 合約地址
:return:
"""
# 合約地址
self.to_address = address
# 讀取abi檔案,并轉為json格式
abi_file = "contracts/HelloWorld.abi"
data_parser = DatatypeParser()
data_parser.load_abi_file(abi_file)
self.contract_abi = data_parser.contract_abi
# 創建BcosClient實體
self.client = BcosClient()
def sendtx(self, fn_name, args=None):
"""
:param fn_name: 對應合約中的函式名
:param args: fn_name的引數
:return: 交易資訊,json格式
"""
if args is None:
sendtx_result = self.client.sendRawTransactionGetReceipt(self.to_address, self.contract_abi, fn_name, [])
else:
sendtx_result = self.client.sendRawTransactionGetReceipt(self.to_address, self.contract_abi, fn_name, [args])
return {"result": sendtx_result}
def call(self, fn_name, args=None):
"""
:param fn_name: 對應合約中的函式名
:param args: fn_name的引數
:return: 交易資訊,json格式
"""
if args is None:
call_result = self.client.call(self.to_address, self.contract_abi, fn_name, [])
else:
call_result = self.client.call(self.to_address, self.contract_abi, fn_name, [args])
return {"result": call_result}
呼叫測驗:
contract_address = "0xa2a802c413d738c98054c5582997c3120d2ebe0b"
cnt = Contract(contract_address)
call_msg = cnt.call("get")
print(call_msg)
send_msg = cnt.sendtx("set", "hello, fengfeng")
print(send_msg)
call_finish = cnt.call("get")
print(call_finish)

客制化Evidence類
上述實作了一個call和sendRawTransactionGetReceipt的HelloWorld類,但是這并不方便我們后端呼叫,我們需要對其進行客制化設計,不把fn_name當成引數傳入,并對每個函式回傳的資料進行處理,
from client.bcosclient import BcosClient
from client.datatype_parser import DatatypeParser
from web3 import Web3
class Evidence_Contract:
def __init__(self, address: str):
self.to_address = address
abi_file = "contracts/EvidenceFactory.abi"
data_parser = DatatypeParser()
data_parser.load_abi_file(abi_file)
self.contract_abi = data_parser.contract_abi
self.client = BcosClient()
def new_evidence_by_evi(self, evi: str):
new_evidence = self.client.sendRawTransactionGetReceipt(self.to_address, self.contract_abi, "newEvidence", [evi])
return {"result": new_evidence["logs"]}
def get_evidence_by_address(self, address: str):
addr = Web3.toChecksumAddress(address)
evidence_msg = self.client.call(self.to_address, self.contract_abi, "getEvidence", [addr])
return {"result": evidence_msg}
def add_signatures_by_evi_address(self, address: str):
addr = Web3.toChecksumAddress(address)
signature = self.client.sendRawTransactionGetReceipt(self.to_address, self.contract_abi, "addSignatures", [addr])
return {
"result": signature["logs"]
}
def verifySigner_by_address(self, address: str):
try:
addr = Web3.toChecksumAddress(address)
signature = self.client.call(self.to_address, self.contract_abi, "verify", [addr])
return {
"result": signature[0]
}
except:
return {
"address": False
}
def get_signer_by_index(self, index: int):
signature = self.client.call(self.to_address, self.contract_abi, "getSigner", [index])
return {
"address": signature[0]
}
def get_signers_size(self):
signers_size = self.client.call(self.to_address, self.contract_abi, "getSignersSize", [])
return {
"size": signers_size[0]
}
def get_signers(self):
signers = self.client.call(self.to_address, self.contract_abi, "getSigners", [])
return {
"signers": signers[0]
}
tips:python沒有address這個資料型別,如果需要傳入address型別的資料,需要用
Web3.toChecksumAddress(address)轉換,
存證合約中涉及到多簽的場景,對一個存證進行多次簽名,
實作思路:切換賬戶發送交易,
只要切換賬戶再呼叫addSignatures即可,
在client/bcosclient.py下的BcosClient類中添加以下內容:
def set_account_by_privkey(self, privkey):
"""
:param privkey: 用戶私鑰
:return:
"""
self.ecdsa_account = Account.from_key(privkey)
keypair = BcosKeyPair()
keypair.private_key = self.ecdsa_account.privateKey
keypair.public_key = self.ecdsa_account.publickey
keypair.address = self.ecdsa_account.address
self.keypair = keypair
def set_account_by_keystorefile(self, account_keyfile):
"""
:param account_keyfile: bing/accounts目錄下的account_keyfile檔案名
:return:
"""
try:
self.keystore_file = "{}/{}".format(client_config.account_keyfile_path,
account_keyfile)
if os.path.exists(self.keystore_file) is False:
raise BcosException(("keystore file {} doesn't exist, "
"please check client_config.py again "
"and make sure this account exist")
.format(self.keystore_file))
with open(self.keystore_file, "r") as dump_f:
keytext = json.load(dump_f)
privkey = keytext["privateKey"]
self.ecdsa_account = Account.from_key(privkey)
keypair = BcosKeyPair()
keypair.private_key = self.ecdsa_account.privateKey
keypair.public_key = self.ecdsa_account.publickey
keypair.address = self.ecdsa_account.address
self.keypair = keypair
except Exception as e:
raise BcosException("load account from {} failed, reason: {}"
.format(self.keystore_file, e))
python內置了一個賬戶(/bin/accounts/pyaccount.keystore),如果不實作切換賬戶的功能就辦法呼叫部署的合約更沒辦法實作多簽,
-
將webase-front匯出的account_keyfile保存在bin/accounts下,即可通過
BcosClient.set_account_by_keystorefile(account_keyfile)切換用戶

-
也可以根據用戶的私鑰來切換賬戶
BcosClient.set_account_by_privkey(privkey)
測驗代碼:
contract_address = "0x8d6327bf7253e87add9d17212cc76dd7ff1d380c"
# 合約地址
a = Evidence_Contract(contract_address)
fengfeng_privkey = "d6f8c8f9106835ccc8f8d0bbc4b5bf32ff5f8941e69f9f50d075684d10dda7be"
fengfeng2_privkey = "619834a32f41fc9dce7809c3063070af3d78fac577a0c12705984eed0b1a3cb"
a.client.set_account_by_privkey(fengfeng2_privkey)
# 切換賬戶
t = a.new_evidence_by_evi("Hello, world")
print(t)
# 創建存證
print(a.get_evidence_by_address(t["result"][0]["address"]))
# 獲取存證資訊
print("==================== 切換賬戶 ====================")
print("==================== 添加多簽用戶 ====================")
a.client.set_account_by_keystorefile("fengfeng.keystore")
# 切換賬戶
print(a.add_signatures_by_evi_address(t["result"][0]["address"]))
# 添加簽名
print(a.get_evidence_by_address(t["result"][0]["address"]))
# 查看存證資訊

通過Flask設計后端API介面
from flask import Flask, jsonify, request, render_template
from evidence_contract import Evidence_Contract
from flask_cors import CORS
app = Flask(__name__)
contract_address = "0x8d6327bf7253e87add9d17212cc76dd7ff1d380c"
fengfeng_privkey = "d6f8c8f9106835ccc8f8d0bbc4b5bf32ff5f8941e69f9f50d075684d10dda7be"
fengfeng2_privkey = "619834a32f41fc9dce7809c3063070af3d78fac577a0c12705984eed0b1a3cb"
CORS(app)
@app.route("/new_evidence", methods=["GET", "POST"])
def new_evidence():
data = request.get_json()
if data is None:
return jsonify({"error": "Pleace input [privkey, evidenceString] by string."}), 400
privkey = data["privkey"]
evidence_string = data["evidenceString"]
evidence = Evidence_Contract(contract_address)
evidence.client.set_account_by_privkey(privkey)
new_evi = evidence.new_evidence_by_evi(evidence_string)
return jsonify(new_evi), 200
@app.route("/evidence/<address>", methods=["GET", "POST"])
def show_evidence(address):
try:
evidence = Evidence_Contract(contract_address)
evi = evidence.get_evidence_by_address(address)
return jsonify(evi), 200
except Exception as e:
return jsonify({"error": e}), 400
@app.route("/addsignatures", methods=["GET", "POST"])
def add_sinatures():
data = request.get_json()
if data is None:
return jsonify({"error": "pleace input [privkey, evidenceAddress] by string."}), 400
privkey = data["privkey"]
evidence_address = data["evidenceAddress"]
evidence = Evidence_Contract(contract_address)
try:
evidence.client.set_account_by_privkey(privkey)
except:
return jsonify({"error": "Please enter the correct private key."}), 400
result = evidence.add_signatures_by_evi_address(evidence_address)
return jsonify(result), 200
@app.route("/verifysigner", methods=["GET", "POST"])
def verify():
data = request.get_json()
if data is None:
return jsonify({"error": "pleace input [signerAddress,] by string."}), 400
evidence = Evidence_Contract(contract_address)
result = evidence.verifySigner_by_address(data["signerAddress"])
return jsonify(result), 200
@app.route("/signer/<int:index>")
def showsigner(index):
evidence = Evidence_Contract(contract_address)
result = evidence.get_signer_by_index(index)
return jsonify(result), 200
@app.route("/signer/lists")
def listsigner():
evidence = Evidence_Contract(contract_address)
result = evidence.get_signers()
return jsonify(result), 200
@app.route("/signer/size")
def get_signer_size():
evidence = Evidence_Contract(contract_address)
result = evidence.get_signers_size()
return jsonify(result), 200
if __name__ == '__main__':
app.run(host="0.0.0.0")
postman測驗介面

至此我們利用Python實作了后端API介面,可以通過這一套API來設計屬于自己的web應用了,
總結
利用Webase-Front + Python-SDK,我們實作了一套API介面并通過這套API介面呼叫了所有的合約介面,
專案倉庫
https://github.com/WeLightProject/Evidence-Sample-Python
參考鏈接
https://github.com/fisco-bcos/python-sdk
https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/sdk/index.html
https://github.com/MIllionBenjamin/Blockchain-FinalProject-SupplyChainFinancialPlatform
關于作者
作者的聯系方式:
微信:thf056
qq:1290017556
郵箱:1290017556@qq.com
你也可以通過 github | csdn | @新浪微博 關注我的動態
我們的公眾號平臺 — (湖師區塊鏈學會)
歡迎各位大大關注我們湖州師范區塊鏈協會的公眾號(湖師區塊人),我們會在這里不定期推送區塊鏈相關的“精神食糧”,

歡迎評論關注+點贊啊!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/266717.html
標籤:區塊鏈
上一篇:【2021年新書推薦】TensorFlow 2 Reinforcement Learning Cookbook
下一篇:Opencl編程的標準開發流程
