1. 程式人生 > >以太坊智慧合約開發環境搭建

以太坊智慧合約開發環境搭建

以太坊合約開發最快速上手是使用remix-ide,用瀏覽器開啟即可使用。不過喜歡折騰的話,就需要手動搭環境了,本文簡單介紹了以太坊開發環境的搭建。

一、 搭建環境

本文使用的作業系統為Ubuntu 16.04。

以太坊開發需要安裝:geth、solc、nodejs、web3.js。

  • geth:用來挖礦、處理交易,執行合約程式碼。
  • solc:用來將合約程式碼編譯為EVM可執行的操作碼。
  • nodejs:提供javascript本地執行環境,web3即執行其上。
  • web3.js:封裝好了一系列以太坊相關的介面。

安裝geth、solc:

sudo add-apt-repository ppa:ethereum/ethereum
sudo
apt-get update sudo apt-get install ethereum solc

安裝nodejs:

sudo apt-get install nodejs

預設安裝的node版本為v4.x.x,這個版本比較低,會有少部分web3的功能無法使用,推薦從官網下載最新穩定版,如v8.x.x。

安裝web3:

cd ~
npm install web3

因為網路原因,國內通過npm安裝模組容易失敗,可以使用淘寶映象

二、建立私有節點

合約程式碼需要執行在以太坊節點上,公鏈成本高速度慢,不適合開發測試,因此我們需要搭建私有節點來開發除錯。

首先,新建私有節點配置檔案genesis.json,輸入以下內容:

{
    "config": {
     "chainId": 123,
     "homesteadBlock": 0,
     "eip155Block": 0,
     "eip158Block": 0
    },
    "alloc"  : {},
    "coinbase"   : "0x0000000000000000000000000000000000000000",
    "difficulty" : "0x20000",
    "extraData"  : "",
    "gasLimit"   : "0x2fefd8",
    "nonce"  : "0x0000000000000042",
    "mixhash"
: "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00" }

使用genesis.json生成私有節點

geth init genesis.json --datadir=.ethereum

會生成兩個資料夾./.ethereum/geth./.ethereum/keystore。前者存放區塊資料,後者存放賬戶資料。

啟動挖礦程式,並對外提供訪問介面

geth --datadir=.ethereum --identity "lixp" --networkid 54321 --nodiscover --rpc --rpcaddr 0.0.0.0 --rpcport 44444 --rpccorsdomain '*' --rpcapi 'db,eth,net,web3,personal,admin,mine' console

在geth終端命令列中輸入下列測試命令。

新建賬戶,檢視餘額:

personal.newAccount()
account = eth.accounts[0]
balance = eth.getBalance(account)
web3.fromWei(balance)

設定賬戶為挖礦賬戶,並開啟挖礦:

miner.setEtherbase(account)
miner.start()

每次啟動後第一次挖礦需要初始化,耗時比較長,當看到新塊列印資訊後,停止挖礦,檢視餘額:

miner.stop()
balance = eth.getBalance(account)
web3.fromWei(balance)

三、 使用web3訪問私有節點

新建test.js,輸入下列程式碼,檢視餘額:

function web3Init()
{
    Web3 = require('web3');
    Net = require('net');
    web3 = new Web3();
    web3.setProvider(".ethereum/geth.ipc", Net);
    D = console.log;
}
function onBalance(mesg)
{
    D("_onBalance:" + mesg);
    balance = mesg;
    D("eth:" + web3.utils.fromWei(balance));
}
function onAccounts(mesg)
{
    D("_onAccounts:" + mesg);
    account = mesg[0];
    web3.eth.getBalance(account).then(onBalance);
}
function getBalance()
{
    web3.eth.getAccounts().then(onAccounts);
}
web3Init();
getBalance();

執行node test.js檢視餘額。

四、 編寫一個簡單的智慧合約

新建people.sol,輸入以下內容:

pragma solidity ^0.4.0;
contract People
{
    string name;
    event newname(string);
    constructor(string _name) public
    {
        rename(_name);
    }
    function rename(string _name) public
    {
        name = _name;
        emit newname(name);
    }
}

編譯:

solc --combined-json=abi,bin,interface people.sol > people.sol.js

編譯輸出為people.sol.js,是一個json格式的資料,包含了合約的元資料(合約名、類方法、引數等)以及釋出到節點的EVM操作碼資料。

五、 釋出合約

新建deploy.js,輸入如下程式碼:

web3Init('.database/geth.ipc');         // 初始化相關模組
cJson = Json(File('people.sol.js'));    // 讀取編譯後的json資料檔案
cAbi = cJson.contracts["people.sol:People"].abi;    // 合約的ABI
cBin = cJson.contracts["people.sol:People"].bin;    // 合約的BIN
cTemp = new eth.Contract(Json(cAbi));   // 新建合約物件
cTrans = cTemp.deploy({                 // ‘合約釋出’交易物件
    data: "0x"+cBin,
    arguments: ['lixp']
});

eth.getCoinbase().then( function(mesg){     // 獲取coinbase
    D("coinbase: " + mesg);
    ethCoinbase = mesg;
    sendTranscation(cTrans, ethCoinbase, '', onDeploy); // 使用coinbase部署合約,第三個引數為新建賬戶時的密碼
});

function onDeploy(mesg){
    D('onDeploy');
    cDeploy = mesg;         // cDeploy中可以檢視合約地址
    cDeploy.events.newname().on('data', function(event){
        D('event newname:');
        D(event.returnValues);
    });
    trans = cDeploy.methods.rename("xiaoming");
    sendTranscation(trans, ethCoinbase, '', D);
}

function sendTranscation(trans, account, accountKey, callback) {
    // 評估交易手續費
    eth.estimateGas( {data: trans.encodeABI()} ).then(
        function (mesg) {
            gas = mesg;
            D("gas:" + gas);
            // 解鎖用於釋出合約的賬戶,否則會出錯
            eth.personal.unlockAccount(account, accountKey).then(
                function (result) {
                    if(result != true){
                        D('unlockAccount failed:' + result);
                        return;
                    }
                    // 將交易傳送到節點,但是如果節點停止挖礦,則交易無法打包到區塊中,
                    // 也就是說交易無法得到執行,callback也不會被觸發。
                    // 需要在節點終端執行miner.start(),開啟挖礦。
                    trans.send({from: account, gas: gas, gasPrice: '30000'}).then(callback);
                }
            );
        }
    )
}

function web3Init(url){
    Web3 = require('web3');
    Net = require('net');
    web3 = new Web3(url, Net);
    Fs=require('fs');
    eth = web3.eth;
    D = console.log;
    File = (name) => {return Fs.readFileSync(name);}
    Json = JSON.parse;
}

執行node deploy.js來發布合約。

  • 釋出合約所需要的賬戶必須有一定的餘額,因為合約釋出、呼叫需要消耗手續費。
  • 釋出合約後,在geth終端可以看到交易的列印, 為了將交易打包到區塊中,需要執行miner.start()。

六、 在瀏覽器中顯示

生成cJson.js,方便瀏覽器呼叫:

echo "cJson = $(cat people.sol.js)" > cJson.js

新建people.html, 輸入如下:

<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
<script src=cJson.js></script> 
<script>
    Web3 = require('web3');
    Net = require('net');
    web3 = new Web3();
    web3.setProvider(".ethereum/geth.ipc", Net);
    eth = web3.eth;
    Json = JSON.parse;
    D = console.log;
    cAbi = cJson.contracts["people.sol:People"].abi;
    address = "0x1234...";  // 釋出合約時得到的地址
    cDeploy = new eth.contract(Json(cAbi)).at(address);
    cDeploy.newname({},{fromBlock:0}).watch(
        (error, log) => {
            D('newname!');
            D(error);
            D(log);
        }
    );
</script>

開啟瀏覽器訪問people.html即可在偵錯程式控制檯看到列印資訊。

node中引入的web3版本為v1.x,網頁中引入的為v0.2.x,這兩個版本的API有一定的差異。

七、 參考資料