以太坊实战——从nodejs终端使用web3模块

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014633283/article/details/83210946

1. 写在前面

一直觉得Ethereum相关的开发工具挺繁杂的,网上关于怎么“编写、部署和调用智能合约”的教程也比较多,但这些教程基本上都是基于truffle框架、geth终端等工具进行合约的部署的调用。既然web3只是nodejs环境下的一个JavaScript模块,我一直想通过最简单、纯粹的nodejs环境去直接使用web3,这样能够对web3模块有个比较立体的认识。于是,便有了这篇博文。

为形成一个完整的合约开发和部署流程,本文按照“编译合约”、“部署合约”和“调用合约”三个步骤来进行讲述。为使得文章讲述更清晰,我们使用一个简单的合约,内容如下所示:

pragma solidity ^0.4.19;

contract Book {
    mapping(uint => string) books;
    event printBookName(string bookName);
    
    function registerBook(uint _bookId, string _bookName) public {
        books[_bookId] = _bookName;
        emit printBookName("Registered successfully!");
    }
    
    function getBook(uint _bookId) public view returns (string) {
        return books[_bookId];
    }
}

2. 实验环境

很多读者在按照网上的教程进行实验时,会出现各种各样的bug,主要是因为软件包版本不同,所以在以后的博客中,我都会列明实验的环境配置。

  • 操作系统: ubuntu16.04
  • node 版本:v8.12.0
  • npm 版本:6.4.1
  • solc 版本:0.4.25
  • ganache-cli 版本:v6.1.8
  • web3 版本:0.20.7

需要注意的是,我们需要部署一条私有链供web3连接,可以采用上一篇博客中介绍的方法从头开始部署。这里我们采用一个更简单的方法,直接借助于ganache-cli工具。

3. 编译合约

编译合约的目的是为了得到abibin,其中abi是个json文件,bin是二进制文件。
编译合约的方式有很多种,比较常见的是通过在线IDE remix和终端工具solc编译。

3.1 常见的编译错误

早期,solc是被集成到web3模块和geth中的,但后来被移除了。所以一些旧的教程上的合约编译步骤可能会出现问题。
具体而言,

  • nodejs console中按照以下命令来编译以上的合约,会出现以下的错误:
    odejs console中编译合约
  • geth中按照以下命令来编译合约,会出现以下的错误:
    geth中编译合约

3.2 推荐的编译方式

3.2.1 Remix编译

比较简单,省略。

3.2.2 solc命令编译

假设我们之前的合约文件名为Book.sol

solc --abi --bin Book.sol

生成的binabi如下图所示:
solc命令编译

4. 部署合约

以下的操作都是在nodejs终端下完成,所以在进行操作之前,需要安装nodejs,并通过命令node进入nodejs终端中。需要注意的是,web3模块的版本必须是0.20.x左右的,如果是1.0.x版本,在创建智能合约及以下步骤都会报错。安装0.20.7版本的脚本为npm install web3@0.20.7
部署合约的脚本如下所示

//使用web3模块
var Web3 = require('web3')

//创建web3实例,并连接私有链(假设私有链监听8545端口)
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

//创建智能合约,参数为solc编译后生成的abi
var bookContract = web3.eth.contract(/*abi*/)

//创建一个变量用于指代主账户,方便后续的操作
var account_0 = web3.eth.accounts[0]

//创建initializer,内同填充合约编译生成的bin,主要用于下一步的合约部署
var initializer = {from: account_0, data: '0x' + /*bin*/, gas: 300000}

//部署合约
var book = bookContract.new(initializer)

5.调用合约

根据是否会更改链上数据,合约的调用分为以下两种:

5.1 更改链上数据

举例来说,上述合约中的registerBook方法会修改books变量中的数据,其调用命令如下所示

book.registerSchoolsendTransaction(1, "Thinking in Java", {from: acount_0, gas: 300000})

函数的调用结果如下图所示:
registerBook调用结果
此种方法一般对应于合约中的非pure非view函数,需要消耗gas,无法直接得到函数的return结果。关于如何返回非pure非view函数的return结果,将在第6节中进行介绍。该方法只会返回一个交易的id

5.2 不更改链上数据

举例来说,上述智能合约中的getBook方法只是做查询工作,而不更改链上数据,其调用命令如下所示

book.getBook.call(1)

函数的调用结果如下图所示:
getBook调用结果
此种方法一般对应合约中的view或者pure函数,不消耗gas,可以直接返回函数的return结果。
补充一点,任何不更改链上数据的调用也可以通过第一种方法(sendTransaction)来实现。但通过sendTransaction来调用函数(即使是pure或者view函数),也只会返回transactionid,如下图所示:
getBook的sendTransaction调用

6. 返回“非pure非view函数”的结果

这种情况一般只能通过监视event来实现,event的定义和调用已经在合约中展示。以下介绍event的监视命令:

// 定义event变量
var printBookNameEvent = book.printBookName()

// 监视event的发生
printBookNameEvent.watch(function(error, result){if(!error){process.stdout.write(result.args.bookName)}})

// 调用相应的函数即可触发该event,打印出相应的值
book.registerBook.sendTransaction(2, "Introduction to Algorithms", {from: account_0, gas: 300000})

以上三个步骤的执行结果分别如下图所示,其中第三张截图中的"Registered Successfully"即是event返回的结果。
定义event变量
监视event的发生
触发event

展开阅读全文

没有更多推荐了,返回首页