如何用python和flask以太坊智能合約開發
在本教程中,我們將編寫一份智能合約(我將進一步解釋),以便在區塊鏈上保留用戶數據。我們將使用python web3(web3的python庫)來開發和部署智能合約。一旦我們在區塊鏈上部署了智能合約。我們將使用flask API與智能合約進行交互以存儲一些數據/信息。我們將它存儲在區塊鏈上,它是不可變的。
環境要求
Python 3.6
安裝
1.創建一個python虛擬環境。
Virtualenv將你的Python軟件包本地化保存在你項目的虛擬環境中,而不是強迫你在系統範圍內安裝軟件包。
$ virtualenv -p /usr/bin/python3.6 venv
$ source venv/bin/activate
2.現在我們需要Ganache
那樣的以太坊測試鏈。
Ganache是以太坊開發的個人區塊鏈,可用於部署合約,開發應用程序和運行測試。
$ npm install -g ganache-cli
3.安裝python web3
Web3.py是一個用於與以太坊交互的python庫。它的API源自Web3.js Javascript API,對於使用過web3.js的人來說應該很熟悉。
$ pip3 install web3
4.Flask
Flask是一個python輕量級框架。
$ pip3 install flask
5.Flask Restful
Flask-RESTful是Flask的擴展,增加了對快速構建REST API的支持。
$ pip3 install flask-restful
- Flask Marshmallow
Flask marshmallow是一個對象序列化/反序列化庫。
$ pip3 install flask-marshmallow
啟動以太坊測試區塊鏈服務器
要部署智能合約,我們應該啟動測試以太坊服務器。我們正在使用ganache進行測試。在終端中鍵入以下命令:
$ ganache-cli
Ganache為我們提供了10個默認測試帳戶,每個帳戶中有100個假ether,用於交易。我們將使用這些帳戶在合約中部署和設置各種值。
我們可以看到gas價格和限制以及部署ganache
的host:port
。我們在部署合約時需要這個。
創建user.sol文件
現在我們將用Solidity編寫智能合約。Solidity是在ethereum上編寫智能合約的語言。智能合約包括我們將在區塊鏈上存儲的數據,數據和getter方法的可選驗證函數,訪問數據的setter方法。
例如,要在區塊鏈上進行考勤註冊,你將擁有一組用戶對象。它將可以訪問用戶的getter,setter方法。由於每個用戶每天只能標記一次出勤,因此你需要一個驗證功能來檢查,智能合約與我們通常用其他任何語言開發的應用程序非常相似。
在下面的文件中,我們使用getter,setter函數構建簡單的用戶合約。
1.在.sol文件中聲明solidity編譯器版本。
pragma solidity ^ 0.4.21;
了解使用的編譯器版本。
$ solidity?—?version
2.導入庫文件Import library。我們應該將庫用於常用的實用程序函數。庫可以只編譯一次並反復使用(點擊這裏獲取一些好的庫資源)。
import“stringUtils.sol”;
3.為用戶聲明合約
contract userRecords {}
4.現在,對於基本演示,我們將存儲有關用戶的名稱和性別信息。因此,使用struct和enum數據類型初始化這兩個變量。
//枚舉類型變量來存儲用戶性別
enum genderType { male, female }
//我們將存儲在以太坊合約中的實際用戶對象
struct user{
string name; genderType gender;
}
5.現在我們將聲明user(struct)
類型的用戶對象。也可以將其聲明為public,以便從合約外部訪問它(有關可見範圍,請單擊此處)。
user user_obj;
6.現在為用戶對象添加getter,setter方法。我們將在區塊鏈上保留每個用戶的信息。我們應該始終公開此方法,因為我們將從合約外部訪問它們。
//設置用戶公共功能
//這類似於db中的持久對象。
function setUser(string name, string gender) public {
genderType gender_type = getGenderFromString(gender);
user_obj = user({name:name, gender: gender_type});
}
//獲取用戶公共功能
//這類似於從db獲取對象。
function getUser() public returns (string, string) {
return (user_obj.name, getGenderToString(user_obj.gender));
}
7.請註意,我們使用了兩個內部函數getGenderFromString()
和getGenderToString()
。讓我們添加這個內部函數。將它們聲明為內部,因為我們不會在外面使用它們。
//用於從string中轉換genderType枚舉的內部函數
function getGenderFromString(string gender) internal returns(genderType) {
if(StringUtils.equal(gender, "male")) {
return genderType.male;
} else {
return genderType.female;
}
}
//將genderType枚舉轉換為字符串的內部函數
(string) {
if(gender == genderType.male) {
return "male";
} else {
return "female";
}
}
我們正在使用stringUtils.equal()
庫函數。由於此版本的solidity不支持使用(==)進行字符串比較。
8.現在我們的user.sol文件合約如下所示:
pragma solidity ^0.4.21;
// import library file
import "stringUtils.sol";
contract userRecords {
// enum type variable to store user gender
enum genderType { male, female };
// Actual user object which we will store
struct user{
string name;
genderType gender;
}
// user object
user user_obj;
//Internal function to conver genderType enum from string
function getGenderFromString(string gender) internal returns (genderType) {
if(StringUtils.equal(gender, "male")) {
return genderType.male;
} else {
return genderType.female;
}
}
//Internal function to convert genderType enum to string
function getGenderToString(genderType gender) internal returns (string) {
if(gender == genderType.male) {
return "male";
} else {
return "female";
}
}
// set user public function
// This is similar to persisting object in db.
function setUser(string name, string gender) public {
genderType gender_type = getGenderFromString(gender);
user_obj = user({name:name, gender: gender_type});
}
// get user public function
// This is similar to getting object from db.
function getUser() public returns (string, string) {
return (user_obj.name, getGenderToString(user_obj.gender));
}
}
使用python腳本編譯和部署solidity文件。
1.在下面的python腳本中,我們需要實例化python-web3測試以太坊節點。我們將設置ganche url為測試以太坊節點。我們將使用下面的w3對象來部署合約。
from web3 import Web3
# web3.py instance
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
2.現在我們將編譯solidity
代碼。為了編譯solidity
代碼,我們使用py-solc,它是用於solidity
編譯器的python擴展。
from solc import compile_files
# 編譯所有合約文件
contracts = compile_files([‘user.sol‘, ‘stringUtils.sol‘])
# 單獨的主文件和鏈接文件
main_contract = contracts.pop("user.sol:userRecords")
library_link = contracts.pop("stringUtils.sol:StringUtils")
3.每當使用import語句編譯.sol文件時。我們還需要鏈接導入文件的部署地址以及主合約。 因此,對於部署所有鏈接首先通過編譯它(如果已經部署然後保存地址)請參見下圖主合約的bin。
當你編譯主合約時,如果你看到它的bin部分,你將找到我們正在導入的庫的_stringUtils.sol:StringUtils ___________
(它也可以用於合約)。 這部分我們應該通過在部署合約之前的庫地址來替換它。
4.然後我們將庫地址與主合約相關聯。
from solc import link_code
def deploy_contract(contract_interface):
#實例化和部署合約
contract = w3.eth.contract(
abi=contract_interface[‘abi‘],
bytecode=contract_interface[‘bin‘]
)
#從已部署的合約中獲取交易哈希
tx_hash = contract.deploy(
transaction={‘from‘: w3.eth.accounts[1]}
)
#獲取tx收據以獲取合約地址
tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
return tx_receipt[‘contractAddress‘]
library_address = {
"stringUtils.sol:StringUtils": deploy_contract(library_link)
}
main_contract[‘bin‘] = link_code(
main_contract[‘bin‘], library_address
)
鏈接後主合約bin的見下圖:
你將看到導入庫的bin已添加。
5.現在使用我們的w3對象部署主合約。使用ethereum account {‘from‘:w3.eth.accounts [1]}
的默認地址進行部署。
def deploy_contract(contract_interface):
# 實例化和部署合約
contract = w3.eth.contract(
abi=contract_interface[‘abi‘],
bytecode=contract_interface[‘bin‘]
)
# 從部署的合約中獲取交易哈希
tx_hash = contract.deploy(
transaction={‘from‘: w3.eth.accounts[1]}
)
# 獲取tx收據以獲取合同地址
tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
return tx_receipt[‘contractAddress‘]
contract_address = deploy_contract(main_contract)
你將在運行ganache測試服務器的選項卡中看到以下這行:
這與合約部署後在tx_receipt
中獲得的信息相同。
6.現在將abi和contract_address存儲在json文件中。這樣我們以後可以在flask api中使用它來存儲合約中的用戶對象。
# 在json文件中添加abi(應用程序二進制接口)和交易收據
with open(‘data.json‘, ‘w‘) as outfile:
data = {
"abi": main_contract[‘abi‘],
"contract_address": deploy_contract(main_contract)
}
json.dump(data, outfile, indent=4, sort_keys=True)
7.現在我們的完整腳本如下所示:
import json
from web3 import Web3
from solc import compile_files, link_code, compile_source
# web3.py instance
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
def deploy_contract(contract_interface):
# Instantiate and deploy contract
contract = w3.eth.contract(
abi=contract_interface[‘abi‘],
bytecode=contract_interface[‘bin‘]
)
# Get transaction hash from deployed contract
tx_hash =contract.deploy(transaction{‘from‘:w3.eth.accounts[1]})
# Get tx receipt to get contract address
tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
return tx_receipt[‘contractAddress‘]
# compile all contract files
contracts = compile_files([‘user.sol‘, ‘stringUtils.sol‘])
# separate main file and link file
main_contract = contracts.pop("user.sol:userRecords")
library_link = contracts.pop("stringUtils.sol:StringUtils")
# print bin part in console you will see ‘stringUtils‘ in that we need to link library address in that bin code.
# to that we have to deploy library code first then link it
library_address = {
"stringUtils.sol:StringUtils": deploy_contract(library_link)
}
main_contract[‘bin‘] = link_code(
main_contract[‘bin‘], library_address)
# add abi(application binary interface) and transaction reciept in json file
with open(‘data.json‘, ‘w‘) as outfile:
data = {
"abi": main_contract[‘abi‘],
"contract_address": deploy_contract(main_contract)
}
json.dump(data, outfile, indent=4, sort_keys=True)
創建flask api以為用戶存儲不同的值
你只需部署一次合約。但是使用它的地址,你會一次又一次地存儲數據。同樣,在db的世界中,你只需定義一次模型/模式,但你將在db中添加不同的行/文檔。
我們將使用flask post api來獲取用戶的用戶信息並返回成功。
from flask import Flask, Response, request, jsonify
from marshmallow import Schema, fields, ValidationError
def check_gender(data):
valid_list = ["male", "female"]
if data not in valid_list:
raise ValidationError(
‘Invalid gender. Valid choices are‘+ valid_list
)
#For api validations
class UserSchema(Schema):
name = fields.String(required=True)
gender = fields.String(required=True, validate=check_gender)
# Initializing flask app
app = Flask(__name__)
# api to set new user every api call
@app.route("/blockchain/user", methods=[‘POST‘])
def user():
body = request.get_json()
result, error = UserSchema().load(body)
if error:
return jsonify(error), 422
return jsonify({"data": result}), 200
由於這不是flask教程,我不會詳細說明這一點,如果flask不熟悉可以看這個flask教程學習下。我們的API用戶將從客戶端獲取數據(curl請求)並對其進行驗證將其返回給客戶端(curl請求)
2.現在我們將初始化web3對象以與已部署的用戶合約進行通信。
from web3 import Web3
# web3.py instance
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
3.現在我們將獲得之前存儲在data.json
文件中的abi和合約地址。
with open("data.json", ‘r‘) as f:
datastore = json.load(f)
abi = datastore["abi"]
contract_address = datastore["contract_address"]
4.選擇交易的默認帳戶地址。每次在合約中為用戶設置新值。你會從錢包裏拿出一些gas。
w3.eth.defaultAccount = w3.eth.accounts[1]
5.最後,你將在以太坊合約中設置api調用用戶對象時獲得的值。
@app.route("/blockchain/user", methods=[‘POST‘])
def user():
# Create the contract instance with the newly-deployed address
user = w3.eth.contract(address=contract_address, abi=abi)
body = request.get_json()
result, error = UserSchema().load(body)
if error:
return jsonify(error), 422
tx_hash = user.functions.setUser(
result[‘name‘],result[‘gender‘]
)
tx_hash = tx_hash.transact()
# Wait for transaction to be mined...
w3.eth.waitForTransactionReceipt(tx_hash)
user_data = user.functions.getUser().call()
return jsonify({"data": user_data}), 200
我們首先使用abi和contract_address獲得部署合約。
user = w3.eth.contract(address=contract_address, abi=abi)
然後我們可以使用合約實例調用任何合約公共函數。在為用戶設置值之後,我們將使用transact方法將其公之於眾。這將在以太坊區塊中添加新的用戶值。
tx_hash = user.functions.setUser(
result[‘name‘],result[‘gender‘]
).transact()
現在我們可以使用call方法獲得已在合約中設置的值,這將調用合約函數而不在區塊鏈中添加任何區塊。
user_data = user.functions.getUser().call()
我們的api文件的最終代碼如下所示。將其另存為app.py
。
import json
from flask import Flask, Response, request, jsonify
from marshmallow import Schema, fields, ValidationError
from web3 import Web3
# web3.py instance
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
w3.eth.defaultAccount = w3.eth.accounts[1]
# Get stored abi and contract_address
with open("data.json", ‘r‘) as f:
datastore = json.load(f)
abi = datastore["abi"]
contract_address = datastore["contract_address"]
def check_gender(data):
valid_list = ["male", "female"]
if data not in valid_list:
raise ValidationError(
‘Invalid gender. Valid choices are‘+ valid_list
)
#For api validations
class UserSchema(Schema):
name = fields.String(required=True)
gender = fields.String(required=True, validate=check_gender)
# Initializing flask app
app = Flask(__name__)
# api to set new user every api call
@app.route("/blockchain/user", methods=[‘POST‘])
def user():
# Create the contract instance with the newly-deployed address
user = w3.eth.contract(address=contract_address, abi=abi)
body = request.get_json()
result, error = UserSchema().load(body)
if error:
return jsonify(error), 422
tx_hash = user.functions.setUser(
result[‘name‘],result[‘gender‘]
).transact()
# Wait for transaction to be mined...
receipt = w3.eth.waitForTransactionReceipt(tx_hash)
user_data = user.functions.getUser().call()
return jsonify({"data": user_data}), 200
運行以下命令以啟動服務器。
$ FLASK_APP=app.py flask run
用curl調用api
$ curl -H "Content-Type: application/json" --request POST -d ‘{"name":"John Doe","gender":"male"}‘ http://localhost:5000/blockchain/user
你也可以在這裏找到完整代碼。
python用web3.py庫開發以太坊來說非常的方便,有興趣的用戶可以關註python以太坊教程,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
如何用python和flask以太坊智能合約開發