如何用Python和Flask框架開發以太坊智能合約

將數據存儲在數據庫中是任何軟件應用程序不可或缺的一部分。不管如何控制該數據庫都有一個該數據的主控。區塊鏈技術將數據存儲到區塊鏈網絡內的區塊中。所以,只要某個節點與網絡同步,它們就會得到區塊中數據的副本。所以,該技術中沒有特定的數據主控。php

在本教程中,咱們將編寫一份智能合約(我將進一步解釋),以便在區塊鏈上保留用戶數據。咱們將使用python web3(web3的python庫)來開發和部署智能合約。一旦咱們在區塊鏈上部署了智能合約。咱們將使用flask API與智能合約進行交互以存儲一些數據/信息。咱們將它存儲在區塊鏈上,它是不可變的。html

環境要求

Python 3.6java

安裝

1.建立一個python虛擬環境。node

Virtualenv將你的Python軟件包本地化保存在你項目的虛擬環境中,而不是強迫你在系統範圍內安裝軟件包。python

$ virtualenv -p /usr/bin/python3.6 venv
$ source venv/bin/activate

2.如今咱們須要Ganache那樣的以太坊測試鏈。android

Ganache是以太坊開發的我的區塊鏈,可用於部署合約,開發應用程序和運行測試。git

$ npm install -g ganache-cli

3.安裝python web3程序員

Web3.py是一個用於與以太坊交互的python庫。它的API源自Web3.js Javascript API,對於使用過web3.js的人來講應該很熟悉。github

$ pip3 install web3

4.Flaskweb

Flask是一個python輕量級框架。

$ pip3 install flask

5.Flask Restful

Flask-RESTful是Flask的擴展,增長了對快速構建REST API的支持。

$ pip3 install flask-restful
  1. Flask Marshmallow

Flask marshmallow是一個對象序列化/反序列化庫。

$ pip3 install flask-marshmallow

啓動以太坊測試區塊鏈服務器

要部署智能合約,咱們應該啓動測試以太坊服務器。咱們正在使用ganache進行測試。在終端中鍵入如下命令:

$ ganache-cli

Ganache爲咱們提供了10個默認測試賬戶,每一個賬戶中有100個假ether,用於交易。咱們將使用這些賬戶在合約中部署和設置各類值。

咱們能夠看到gas價格和限制以及部署ganachehost: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進行區塊鏈以太坊開發的詳解。

另外其餘語言能夠學習的以太坊教程以下:

  • web3j教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
  • 以太坊教程,主要介紹智能合約與dapp應用開發,適合入門。
  • 以太坊開發,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和事件等內容。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和事件等。

這裏是原文

相關文章
相關標籤/搜索