金融科技:使用Python搭建以太坊智能合約應用(一)

背景

自2019年10月,越來越多的行業開始探索區塊鏈項目的應用。其中除了IT行業外,便屬金融業最爲敏感,許多金融機構都在研究區塊鏈。

本蒟蒻目前在北京一家金融機構的金融科技部門實習,接觸到區塊鏈技術,這篇文章整理了本人在學習關於利用Python開發以太坊智能合約的一些簡單內容,供自己以後回顧和與大家分享。

因爲本蒟蒻水平非常有限,如有錯誤,歡迎各路大佬指正。

區塊鏈

什麼是區塊鏈

簡單來講,區塊鏈是一個鏈式數據結構,由一個個”區塊“組成。通過一定的機制,來保證如果某人想要修改區塊鏈上的數據,其需要花費極其高昂的成本才能不被人發現數據被修改過。而且每一個區塊鏈的參與者都存儲了整條區塊鏈的數據的(其實也有特殊的情況),所以其不僅要修改自己存儲的數據,還要想辦法修改其餘至少50%的參與者存儲的數據,這計算成本是極其高昂的。即使我們假設一個人有足夠的資源可以在有限的時間內做到(幾乎不可能),他也不會那麼做。因爲這樣做,會使得整個區塊鏈系統不再被認可,人們不會承認其數據的價值。

區塊鏈能用來幹什麼

從上面得知,區塊鏈具有防篡改性(當然還有其他優秀的特性)。也就保證了數據的完全。如果兩個人通過區塊鏈達成一筆交易,那麼誰也不能耍賴。所以區塊鏈技術可以使兩個陌生人可以在交易時信任彼此。區塊鏈解決的是人與人在生產過程中的信任問題。人們用區塊鏈交易不用擔心受騙、反悔等等。
人工智能、大數據和區塊鏈技術的發展,分別對應了當今社會生產力、生產資料和生產關係的深刻變革。區塊鏈無疑會是改變世界的技術,只是現在人們還沒有完全挖掘出其應用的價值。就像曾經的互聯網一樣。

什麼是智能合約

大家聽說過的比特幣是區塊鏈應用的1.0版本,以太坊是區塊鏈應用的2.0版本。而智能合約是以太坊中的重要概念。智能合約是部署在以太坊中的一些交易規則,用戶調用智能合約的特定接口,便可以自動執行特定的交易,同時被記錄到鏈中。比如我可以寫一個領養寵物的智能合約,其他用戶可以通過該智能合約,選擇要領養的寵物以及支付費用,智能合約會自動改變該寵物的主人的記錄,並上傳到鏈中。

開發智能合約的官方指定語言是Solidity。這篇帖子介紹如果利用Python進一步包裝智能合約,來開發具體的區塊鏈應用。

項目編寫

前期準備

1.下載ganache
快速生成虛擬的私有鏈節點,用於開發測試
下載地址:https://www.trufflesuite.com/ganache
我們下載得到的是客戶端版本,也可以安裝其命令行版本:ganache-cli,但安裝命令行版本需要先按照nodejs和npm.所以這裏暫時不使用命令行版本。
下載得到Ganache-2.x-win-x64.appx ,解壓後打開其app目錄下的Ganache.exe即可。
效果圖:
在這裏插入圖片描述
我們後期開發調用該私有鏈時,通過127.0.0.1:7545接口訪問即可。(即圖片中的RCP SERVER)

2.在pycharm中安裝solidity插件
目前來看,開發智能合約貌似繞不開solidity。但可以用Python來控制solidity的編譯,調用智能合約.

開發solidity的IDE有很多,官方推薦Remix.但爲了讓整個項目管理起來更方便,我們直接在Pycharm中安裝solidity插件,在Pycharm中開發solidity.

安裝方式:打開Pycharm,依次選擇Settings–>Plugins,搜索solidity,下載Intellij-Solidity.
在這裏插入圖片描述
若通過上述方式下載失敗,則直接通過官網下載壓縮包https://plugins.jetbrains.com/plugin/9475-intellij-solidity,再解壓到Pycharm安裝目錄的plugins文件夾下即可。
正確配製後,我們新建文件時,就會出現Solidity文件的選項。
在這裏插入圖片描述

3.下載solidity編譯器
上一步在pycharm中安裝的solidity插件應該(未考證)並不包含對solidity代碼的編譯模塊,只是提供了代碼高亮、代碼提示等功能。所以我們需要下載solidity編譯器。
下載地址:https://github.com/ethereum/solidity/releases。這裏我們下載的是0.4.21的windows版本:solidity-windows .zip.
下載完成後進行解壓:
圖片: https://uploader.shimo.im/f/Mh2lXJQDd3gYAATA.png
接下來,爲了方便使用,我們將solc.exe配置到環境變量的Path中。
在這裏插入圖片描述
配置環境變量的主要原因是方便調用solc.exe。例如,我們在cmd中調用solc.exe時,直接在根目錄輸入solc指令即可,而不必cd到solc.exe的解壓目錄下。
在這裏插入圖片描述
4.在Pycharm中編譯solidity文件。
我們安裝完solidity編譯器後,如果需要編譯solidity文件,可以通過在cmd中調用相關指令來實現。但這顯然比較麻煩。我們希望在Pycharm中直接編譯solidity,所以需要簡單配置一下。

打開Pycharm中的file-Settings-Tools-External Tools,點擊‘+’,填入以下內容。
其中三個配置參數爲:
Program:D:\solidity-windows\solc.exe(這個需要換成自己的solc.exe的路徑)
Arguments:–abi --bin FileNameFileName -o ProjectFileDirProjectFileDir\CompiledSolidity (solc的指令)
Working directory:FileDirFileDir (將目錄切換到項目所在目錄中來)
圖片: https://uploader.shimo.im/f/9dvFHICzeXQ9NsEX.png

當需要編譯時,我們只需要右鍵單擊.sol文件,選擇該工具即可。

圖片: https://uploader.shimo.im/f/LEwIqvhxpfELA3sx.png
之後便會出現CompiledSolidity文件夾,裏面包含了編譯好的智能合約的相關文件,我們將會在Python中使用這兩個文件。
圖片: https://uploader.shimo.im/f/A7WvzvgNq0UVCVsA.png
而文件夾的名字是在上面配置工具時,
Arguments:–abi --bin FileNameFileName -o ProjectFileDirProjectFileDir\CompiledSolidity中的-o參數決定的。讀者可自行修改。
兩個文件的名字是由solidity文件中定義的合約名稱決定的。我們編譯的文件中定義了名爲IntDemo的智能合約。
在這裏插入圖片描述

5.安裝web3.py
web3.py是以太坊官方維護的Python版rpc接口封裝庫,用來完成Python和智能合約的交互。
我們利用pip install web3即可安裝完成。

6.測試連接節點
我們在Python中開發以太坊智能合約,首先需要連接到以太坊節點、獲取賬戶。
我們這裏使用上面介紹的Ganache來創建本地的虛擬節點,同時生成10個虛擬賬戶。我們將用這些賬戶來完成合約的測試。
首先我們需要打開Ganache,創建一個本地的虛擬以太坊節點。該節點的地址爲http://localhost:7545
在這裏插入圖片描述
下面我們在Python中連接該以太坊節點,並輸出創建的賬戶。

from web3 import Web3
w3 = Web3(Web3.HTTPProvider('http://localhost:7545'))
accounts = w3.eth.accounts
print(accounts)

運行上述代碼,便可以打印出10個賬戶的Address.這樣便代表連接成功了。

上面的步驟全部成功實現後,我們開發的環境就搭建完成了。後面的工作就是利用solidity編寫智能合約+Python來進一步包裝智能合約,使其可以方便得應用。

**

項目測試

**
因爲我們現在的首要目標是整理用Python開發以太坊項目的過程,具體Solidity的語法不是我們關注的重點,所以我們這裏直接使用網上的一個智能合約代碼來研究,我們只需要知道,該程序創建了一個名爲CrowFunding的智能合約,合約中定義了幾個結構體和若干函數即可。

pragma solidity ^0.4.21;
// 主要完成產品的衆籌
contract CrowdFunding{
    
    // 投資者是結構體
    struct Funder{
        address addr;  // 投資者地址
        uint amount;   // 投資金額
    }
    
    // 採用結構體來描述衆籌產品
    struct Product{
        address addr;   // 如果衆籌成功,則金額會轉到當前地址
        uint goal;      // 預期衆籌的目標,如果達到此目標則說明衆籌成功
        uint amount;    // 實際衆籌的金額
        uint funderNum;    // 統計投資者的人數,缺省值爲0
        // 映射類型,統計當前產品的投資者
        mapping(uint => Funder) funders;
    }
    
    // 平臺要統計衆籌的產品數量
    uint count;  
    // 此映射主要記錄平臺的衆籌產品
    mapping (uint => Product) public products;
    // 添加衆籌產品信息   
    function candidate(address addr, uint goal) returns (uint){
        // 結構體是不需要new,此處按照結構體聲明的變量順序進行賦值
        products[count++] = Product(addr, goal*10**18, 0, 0);
    }
    
    // 此函數實現對產品進行衆籌功能
    function vote(uint index) payable {
        // 通過索引獲取要衆籌產品信息
        Product p = products[index];
        // 創建投資者,並且存儲到產品衆籌映射中
        // msg.sender:當前函數調用者,就是衆籌者, msg.value:衆籌金額是調用函數時傳入的value值
        p.funders[p.funderNum++] = Funder({addr: msg.sender, amount: msg.value});
        // 把當前衆籌金額追加到amount中
        p.amount += msg.value;
    }
    
    // 檢測當前產品衆籌是否成功(如果成功則衆籌金額轉到產品提供的地址)
    function check(uint index) payable returns (bool){
        Product p = products[index];
        // 判斷當前衆籌金額是否大於設置金額
        if(p.amount < p.goal){
            return false;
        }
        // 衆籌成功,當前金額要轉給產品地址
        uint amount = p.amount;
        // 初始化amount
        p.amount = 0;
        p.addr.transfer(amount);  // 如果失敗則返回爲false
        // if(!p.addr.send(amount)){
        //     throw;
        // }
        return true;
    }
}

部署合約
下面介紹如何利用Python部署該智能合約到我們的私有鏈。
首先,我們需要編譯上述Solidity。利用上述介紹的方法編譯即可,這裏我們使用擴展工具來編譯,因爲其在編譯完成後會自動生成存儲我們需要的信息的文件到指定文件夾。
在這裏插入圖片描述
導入需要的包並連接到我們的私有鏈,同時獲取鏈上的賬戶信息。

from web3 import Web3
import json

w3 = Web3(Web3.HTTPProvider('http://localhost:7545'))
accounts = w3.eth.accounts

緊接着,加載上一步編譯生成的abi和bin文件。
注:fn_addr文件用來存儲合約地址。在合約成功部署後,會得到一個合約地址。之後便是通過該地址對合約進行交易。所以保存該地址是有必要的。

artifact = 'CrowdFunding'
fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_bin = 'CompiledSolidity/{0}.bin'.format(artifact)
fn_addr = 'CompiledSolidity/{0}.addr'.format(artifact)

with open(fn_abi,'r') as f:
  abi = json.load(f)
with open(fn_bin,'r') as f:
  bin = f.read()

隨後我們便可以利用abi和bin生成智能合約的一個對象

factory = w3.eth.contract(abi=abi,bytecode=bin)

之後通過發送新的公共交易來部署合約。這裏我們使用第一個來賬戶來部署該合約。

tx_hash = factory.constructor().transact({'from':accounts[0]})

發送交易後,我們需要等待交易被接收。

receipt = w3.eth.waitForTransactionReceipt(tx_hash)
print(receipt)

當交易被鏈正確接收後,可以打印receipt的值,其中記錄了該筆交易和鏈相關的信息,合約的地址就在其中:

AttributeDict({
'transactionHash': HexBytes('0xb4d53764c7cba24b36cc02842af093ad261d9d8c0e04f963409588d4ae8b29fd'), 
'transactionIndex': 0, 
'blockHash': HexBytes('0x4f5a86f62bd1b33f0e2e90dcb7bf973d19117e44ea50c842224d8829b9d4381b'), 
'blockNumber': 7,
'from': '0xA77bbC612F03A9064aebcCA593dc7bF5A0aFb836', 
'to': None, 
'gasUsed': 347082, 
'cumulativeGasUsed': 347082, 
'contractAddress': '0x6C5602a2c4d7D96479B0006edF46fd9756De7AE4', 'logs': [], 
'status': 1, 
 ......})

最後保存一下合約的地址

with open(fn_addr,'w') as f:
  f.write(receipt.contractAddress)

部署完成合約後,接下來就是按照Web3.py提供的接口,對智能合約中定義的方法進行調用。
我們可以通過**Contract.functions.myMethod()**來調用合約中的函數。其中Contract是合約對象,myMethod是合約中定義的函數。例如:

factory.functions.candidate('0xEF43AAeaAD58b47250654efd889137Bd116e92fc',10).transact({'from':accounts[1],'to':receipt.contractAddress})

便調用了上述合約中的candidate函數,並傳入了需要的參數,調用者是第一個賬戶。

調用合約
在合約部署到鏈上後,如果我們想要執行合約。可以分爲兩步:1.生成合約對象 2.通過web3.py與合約交互。
生成合約對象有兩種方式:
第一種:

fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_bin = 'CompiledSolidity/{0}.bin'.format(artifact)

with open(fn_abi,'r') as f:
  abi = json.load(f)

with open(fn_bin,'r') as f:
  bin = f.read()

factory =w3.eth.contract(abi=abi,bytecode=bin)

也就是通過abi和bin實例化一個智能合約對象。這裏的abi和bin便是上面部署合約時保存的內容。

第二種:

fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_addr = 'CompiledSolidity/{0}.addr'.format(artifact)

with open(fn_abi,'r') as f:
  abi = json.load(f)

with open(fn_addr,'r') as f:
  addr = f.read()

factory = w3.eth.contract(abi=abi,address=Web3.toChecksumAddress(addr))

這種方式是通過abi和地址實例化一個智能合約。

這裏建議使用第二種方式。因爲使用第一種方式實例化的智能合約對象,在調用其函數(也就是交易)時,我們仍要在transact中填寫’to’字段(也就是智能合約的地址)。而用第二種方式,也就是通過地址實例化的對象,便可以省略‘to’字段。(至少我用上述的智能合約測試時可以省略)。

生成合約對象後,我們要做的便是調用合約中的函數來修改合約的狀態或者查詢合約中的數據。

factory.functions.candidate(accounts[2],10).transact(
                                           {'from':accounts[1]})

例如上述,便是調用了上述智能合約的candidate函數。我們這裏暫時不用知道candidate函數的具體內容,只需要知道該函數修改了一個“重要”變量(也就是權限爲 public,可供外部查看的變量)的值。而在以太坊智能合約中,一旦這種“重要變量“的值發生改變,其狀態便會更新到以太坊中。而最後的transact({})便是發起交易,{}中要給出交易的一些參數,比如上面就給出了’from‘,也就是交易發起者的地址。該交易一旦被確認,便會被記錄到區塊鏈中。
而會修改區塊鏈狀態的函數沒有返回值的(或者更準確地說,它會返回交易的ID),而如果你想要查看一些智能合約的數據。可以有兩種途徑。
1.編寫相應的get函數,就像Java中的POJO類一樣。

2.對應那些”重要的變量“(也就是權限爲public的變量),solidity編譯器會自動生成一個以變量名爲函數名的getter函數。我們通過該函數來獲取solidity中我們感興趣的變量的值
例如:

count=factory.functions.count().call()

上述便是調用了count()來獲取合約中count的值的代碼。因爲我們調用該函數不會改變區塊鏈的狀態,所以最後用的是.call()而不是上述的.transact().
另外,對於基礎的數據類型,我們調用其getter函數會直接返回相應變量的值。
但對於非基礎類型的數據,比如數組、映射等等,我們調用其getter()函數時需要傳入參數。對於數組,我們要傳入一個int值作爲索引,來獲取數組中對應位置的元素的值(是的,一次只能獲取一個元素,而無法獲取整個數組)。相應的,對於映射,便是傳入key作爲參數了。

現在,我們已經大體知道了如何部署並調用合約。
後面要完成的工作便是學習solidity的語法細節和研究web3.py的開發文檔,再結合一些Python的框架,開發出智能合約的應用了。

剩下的以後再補充,歡迎指教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章