以太坊學習筆記:經過web3.js與智能合約交互

web3.js是以太坊提供的一個Javascript庫,它封裝了以太坊的JSON RPC API,提供了一系列與區塊鏈交互的Javascript對象和函數,包括查看網絡狀態,查看本地帳戶、查看交易和區塊、發送交易、編譯/部署智能合約、調用智能合約等,其中最重要的就是與智能合約交互的API。javascript

下面就介紹如何使用web3.js提供的接口調用智能合約。java

系統和軟件

  • Ubuntu 16.04 64位
  • nodejs 6.10.0
  • npm 3.10.10

示例合約

本文如下面的MetaCoin合約爲例,說明在應用中使用web3.js操做智能合約的方法。node

// 本文中用到的MetaCoin合約
pragma solidity ^0.4.2;

contract MetaCoin {
	mapping (address => uint) balances;

	event Transfer(address indexed _from, address indexed _to, uint256 _value);

	function MetaCoin() {
		balances[tx.origin] = 10000;
	}

	function sendCoin(address receiver, uint amount) returns(bool sufficient) {
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		Transfer(msg.sender, receiver, amount);
		return true;
	}

	function getBalance(address addr) returns(uint) {
		return balances[addr];
	}
}

這個合約有三個函數:git

  • MetaCoin:構造函數,在合約被部署到區塊鏈時執行
  • getBalance:返回某帳戶的餘額,它只讀數據,不會修改數據
  • sendCoin:向另外一個帳戶發送指定數量的MetaCoin,它會改變狀態變量balances

啓動一個以太坊節點,將上面的合約部署到區塊鏈中,並記錄下合約的地址,能夠經過truffle部署,具體參考這篇文章。 接下來就能夠按照下面的步驟在項目中經過web3.js來使用這個合約。github

添加web3到項目中

首先新建一個Nodejs項目並初始化:web

$ mkdir web3test && cd web3test
$ npm init

會提示輸入項目信息,所有默認便可。
接下來下載web3.js到項目中:npm

$ npm install web3 --save

以上命令會將web3.js下載到web3test/node_modules目錄下,其中--save參數會web3.js添加到package.json配置文件中。json

建立web3對象

要使用web3.js與區塊鏈交互,須要先建立web3對象,而後鏈接到以太坊節點。 在web3test目錄下新建index.js文件,在其中輸入如下代碼:網絡

var Web3 = require("web3");
// 建立web3對象
var web3 = new Web3();
// 鏈接到以太坊節點
web3.setProvider(new Web3.providers.HttpProvider("http://localhost:8545"));

獲取已部署的合約實例

要使用智能合約,必須先從區塊鏈中獲取到合約實例,獲取合約實例須要合約的ABI和合約的地址:app

// 合約ABI
var abi = [{"constant":false,"inputs":[{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"}],"name":"sendCoin","outputs":[{"name":"sufficient","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}];
// 合約地址
var address = "0xb2cdd356e58280906ce53e1665697b50f88aac56";
// 經過ABI和地址獲取已部署的合約對象
var metacoin = web3.eth.contract(abi).at(address);

metacoin就是獲取到的合約對象實例,此時metacoin對象中就包含了與合約函數同名的Javascript函數,以後就能夠經過metacoin對象來調用合約中的函數了。

調用合約函數

MetaCoin合約中有兩個函數:getBalancesendCoin,能夠經過metacoin對象直接調用這兩個函數。

首先來看getBalance,因爲getBalance函數只是從區塊鏈中讀數據,而不修改數據,所以咱們經過在getBalance後面加上.call()的方式調用:

var account_one = web3.eth.accounts[0];

var account_one_balance = metacoin.getBalance.call(account_one);
console.log("account one balance: ", account_one_balance.toNumber());

這裏:

  • 在getBalance後加上.call()來顯式指明用call的方式調用
  • 經過call的方式調用能夠獲得getBalance函數的返回值
  • 經過call的方式調用的函數只在節點本地虛擬機中執行,不會產生交易,不會花費費用,不會修改數據

下面來看sendCoin函數,因爲sendCoin要修改合約內部的數據,因此要使sendCoin生效,必需要向區塊鏈發送交易,能夠在sendCoin後面加上.sendTransaction()來指明這是一筆交易:

var account_one = web3.eth.accounts[0];
var account_two = web3.eth.accounts[1];
// 提交交易到區塊鏈,會當即返回交易hash,可是交易要等到被礦工收錄到區塊中後才生效
var txhash = metacoin.sendCoin.sendTransaction(account_two, 100, {from:account_one});
console.log(txhash);

這裏:

  • sendCoin函數後加上.sendTransaction()指明要向區塊鏈發送交易
  • 合約代碼中sendCoin函數只有兩個參數,而在web3中經過.sendTransaction()調用合約函數的時候須要增長最後一個參數,它是一個javascript對象,裏面能夠指定from/value/gas等屬性,上面的例子用from來指定交易的發送者
  • 上面的調用語句執行後,會向區塊鏈提交一筆交易,這筆交易的發送者是account_one,接收者是metacoin的地址,交易的做用是以account_two100做爲參數執行合約的sendCoin函數
  • 函數會當即返回交易的hash,代表交易已經提交到區塊鏈,可是並不知道交易什麼時候處理完成,交易要等到被曠工收錄到區塊中後纔會生效

監聽合約事件

當經過.sendTransaction()調用合約的時候,交易會被提交到區塊鏈進行處理,這個處理須要必定的時間,若是須要等交易完成以後再執行其餘操做,就必需要知道交易什麼時候完成,那麼如何知道交易什麼時候完成呢?能夠經過監聽合約事件來實現。

在合約中能夠定義事件,事件能夠帶有參數,在合約函數內部完成某些操做時,能夠觸發事件,向外界傳達一些信息。例如,在MetaCoin合約中定義了一個事件叫作Transfer,表示一個轉帳的事件,它帶有三個參數:交易的發送者、接受者、轉帳數量。在sendCoin函數中,轉帳成功後就會觸發Transfer事件,將對應的參數傳給該事件,這樣外部監聽到事件後,能夠取出事件的參數來得到交易發送者、接收者、數量。同時事件中還帶有其餘信息,好比交易hash等。

在web3中使用事件,要首先獲取事件對象,而後監聽事件,若是事件發生,就會在回調函數中獲取到事件信息:

// 獲取事件對象
var myEvent = metacoin.Transfer();
// 監聽事件,監聽到事件後會執行回調函數
myEvent.watch(function(err, result) {
    if (!err) {
        console.log(result);
    } else {
        console.log(err);
    }
    myEvent.stopWatching();
});

// 輸出:
{ address: '0xb2cdd356e58280906ce53e1665697b50f88aac56',
  blockNumber: 651,
  transactionIndex: 0,
  transactionHash: '0xcc71bc2824cc84d1ee831c46189e3a80cf0af05697ba0370693aa97390c8067b',
  blockHash: '0x1d53f04206f3926d0f311b1230a9dd0b0c5aadac35b169a6a609e384ab130c6f',
  logIndex: 0,
  removed: false,
  event: 'Transfer',
  args: 
   { _from: '0x68b73956d704007514e9257813bdc58cdf3c969a',
     _to: '0x9c3c1a2f5ef913fac44f0348a78f68d835f3f26e',
     _value: { [String: '100'] s: 1, e: 2, c: [Object] } } }

從輸出能夠看出,獲取到的事件信息包括事件的參數、事件名、區塊號、交易hash等。

經過檢測事件中的transactionHash與調用合約函數返回的交易hash是否一致,能夠斷定某一筆交易是否已完成:

var account_one = web3.eth.accounts[0];
var account_two = web3.eth.accounts[1];

var account_one_balance = metacoin.getBalance.call(account_one);
console.log("account one balance:", account_one_balance.toNumber());

var txhash = metacoin.sendCoin.sendTransaction(account_two, 100, { from: account_one });

var myEvent = metacoin.Transfer();
myEvent.watch(function (err, result) {
    if (!err) {
        if (result.transactionHash == txhash) {
            var account_one_balance = metacoin.getBalance.call(account_one);
            console.log("account one balance after sendCoin:", account_one_balance.toNumber());
        }
    } else {
        console.log(err);
    }
    myEvent.stopWatching();
});

// 輸出:
account one balance: 7000
account one balance after sendCoin: 6900

watch中的回調函數若是被執行,說明事件已被觸發,也就是說某筆交易已經處理完,檢查交易hash是否一致,能夠斷定產生這個事件的交易是不是本身發送的交易,若是是,就能夠進行其餘操做,好比查詢最新的餘額。

相關文章
相關標籤/搜索