以太坊实践整理(四)Truffle智能合约开发框架

相比Remix的轻量级智能合约开发,Truffle是世界级的DApp开发框架,使用Truffle开发有以下优点:

  • 内置智能合约编译,链接,部署和二进制(文件)管理。
  • 可快速开发自动化智能合约测试框架。
  • 可脚本化、可扩展的部署和迁移框架。
  • 可管理多个不同的以太坊网络,可部署到任意数量的公共主网和私有网络。
  • 使用 ERC190 标准,使用 EthPM 和 NPM 进行包装管理。
  • 支持通过命令控制台直接与智能合约进行交互。
  • 可配置的构建管道,支持紧密集成。
  • 支持在Truffle环境中使用外部脚本运行器执行脚本。

Truffle 要求我们有一个运行的以太坊客户端,它支持标准的JSON RPC API 接口。可选的客户端有很多,本地开发建议用Ganache这个私链神器配合。

Ganache客户端

在以太坊主链开发成本太高,而测试网络确认交易也是需要等待矿工打包区块,因此我们需要搭建最适合于本地开发的私链。

Ganache的前身是TestRPC,Ganache可以帮助我们快速启动一个以太坊私链来做开发测试、执行命令、探测区块链状态等。Ganache模拟的是内存中的区块链,它在执行交易时是实时返回,而不等待默认的出块时间,这样我们就可以快速验证代码。它同时还是一个支持自动化测试的功能强大的客户端。

Ganache支持命令行及图形用户界面两种方式的安装和使用。

ganache图形用户界面安装与使用

访问https://www.trufflesuite.com/...下载安装Ganache

打开Ganache,该过程已经帮你创建一个私链并连接上去了,默认创建了10个备用账户:

以太坊实践整理(四)Truffle智能合约开发框架_第1张图片

图形用户界面非常直观,启动后稍微花几分钟看看就能了解清楚它大致为我们提供了什么。

ganache-cli命令安装

Ganache是用JS代码编写的,官方也提供了ganache-cli工具,我们可以直接通过npm安装ganache-cli:

npm install -g ganache-cli

安装好后,执行以下命令即可创建一个私链:

ganache-cli

输出10个默认账户地址及私钥、钱包助记词和路径,以及Gas Price、Gas Limit、Call Gas Limit、JSON-RPC监听的地址和端口:

Last login: Thu Sep  9 12:47:51 on console

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
MacBook:~ zhutx$ sudo npm install -g ganache-cli
Password:

changed 6 packages in 4s

2 packages are looking for funding
  run `npm fund` for details
MacBook:~ zhutx$ ganache-cli
Ganache CLI v6.12.2 (ganache-core: 2.13.2)

Available Accounts
==================
(0) 0x46C4deA8cF817d89cc4dFC98e8FA845F6BC3D57C (100 ETH)
(1) 0x4592e162Cc5B160B7671AD92e853f4Ba9657384f (100 ETH)
(2) 0x1b8eb1f19B9a4E2CC0D47dfFfb310b03Fe505F6B (100 ETH)
(3) 0x93721e2617B0A430a720Ac185A27A756177e395A (100 ETH)
(4) 0x4c5ce9257DEc2DFA845918DE53192E3994D097B9 (100 ETH)
(5) 0xa51d1dd429a4b19e1d83c1F50A659F5F0b0B957e (100 ETH)
(6) 0xa0937d9D62b0e6CD669C96FaCfd41C1cdBBa515D (100 ETH)
(7) 0x9399B1479DD232128ba739FBDD10AFa240a4108B (100 ETH)
(8) 0xe5dffcF3725e4641300a57F279A147F7f29131c6 (100 ETH)
(9) 0x256B285A933913cadC01B20bc71295852e15FE83 (100 ETH)

Private Keys
==================
(0) 0x8764de67a305cf1c9ac9d3c20e6e77cd0f5e5cb46effa547f03faeea4da180a6
(1) 0x0812e9862a6628f74aa1f67885109a3896e6decdc7df7760700de1b693bb020f
(2) 0xf982a9613fcc1f2ea9f0f65b243f04802cabf9a6022e2f944b1b061f5902146c
(3) 0x7a81f3b4e206cb943db75a3b6d3c6a30a0f64677f5258d6f1349d7d84ae7b957
(4) 0x1d1dd875579563dcf0356db8c586f2eb8a76e7b2e6358f1b98dcd2b72c99a3ab
(5) 0xd0d4081c6d1e1a06304d2609d18a8e3955f0e66c433ed3253d52580b7887691e
(6) 0x57cbc15723faef1126a773bcf5d6e7fdbfc7db57459e8ce96a4b0bc6d33956f0
(7) 0xa8abb8b9f22c3cb2d3dd3cea473e0a88208a9c0f76415559a6f0e92cc208c322
(8) 0x53a19198bd00eb82d1d983c5d4df7d6e40a1ca7141cf7c14bc060ce525b9c220
(9) 0xdbcdd369f196e7ec281af5f5ab73975bf22bc692565ab3c534a7aaa0cd123b13

HD Wallet
==================
Mnemonic:      cry junk pretty grace shadow chalk load snow satisfy shield multiply emerge
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545

Ganache常见命令参数

调整挖矿时间(Ganache默认是在交易产生时进行挖矿):

// 10秒产生一个区块
ganache-cli -b 10

指定主机端口与网络ID:

// 指定IP,端口及网络ID
ganache-cli -h 127.0.0.1 -p 8545 -i 8888

设置gas价格和gas上限:

ganache-cli -g 20000000
ganache-cli -l 10000000

输出RPC调用请求体:

ganache-cli -v

-v不是version缩写,而是verbose的意思,RPC调用默认只输出方法名,如eth_getBlockByNumber,而使用-v则会输出请求体,例如:

{
  "id": 5156508106328661,
  "jsonrpc": "2.0",
  "params": [
    "0x2",
    true
  ],
  "method": "eth_getBlockByNumber"
}

指定默认生成账户的以太币:

ganache-cli -e 1000

指定默认生成的账户数量:

ganache-cli -a 50

助记词相关:

ganache-cli -d
ganache-cli -m "boil razor arrest first space chicken social explain leader soon unique upset"
ganache-cli -s "hello"

-d:让Ganache启动节点时使用固定的预定义助记词,这样其他连接Ganache的轻钱包不用每次重新导入助记词。
-m:可指定助记词,使用相同的助记词会生成一个相同的HD钱包;
-s:指定一个种子用来生成助记词,然后使用助记词生成HD钱包,相同的种子会生成相同的助记词,从而生成相同的HD钱包。

指定账户:

ganache-cli --account=",balance" [--account=",balance"]

锁定与解锁账户:

// 使用--secure --unlock
ganache-cli --secure --unlock "0x67a3119994f3fc7b384e086e443bf7a73a96a45c06eae3d1b163586ebc8e6f22" --unlock "0xac0603889ccee85ff0075de364d4fc92d383cec57c2a2c3465404c8296feab15"
// 或者使用-n -u
ganache-cli -n -u 0 -u 1

在工程中引用Ganache的Provider

Ganache本质上是一个使用JavaScript实现的以太坊客户端。Ganache图形界面和ganache-cli都只是对Ganache做了一些封装,然后在Node.js运行而已,所以除了使用封装好的图形界面和ganache-cli命令行,还可以通过JavaScript直接使用Ganache相关的功能。

很多轻钱包或者其他工具基本都使用了web3.js,web3.js是以太坊相关的库,它本身没有以太坊数据,所以需要连接以太坊节点,并且通过以太坊节点提供的JSON-RPC API来获取对应的数据。Provider就是web3.js处理节点连接的模块。

我们任意建立一个文件夹,然后在文件夹下面执行命令:

npm init --yes
npm i web3
npm i ganache-cli

上面的命令就是初始化一个工程,然后安装web3和ganache-cli依赖。我们建立一个account.js文件,内容如下:

// 引入web3.js依赖
const Web3 = require("web3");
// 引入ganache-cli依赖
const ganache = require("ganache-cli");
// 使用Ganache创建一个provider给web3.js使用
var web3 = new Web3(ganache.provider());
// getAccounts获取账户信息,返回的是一个Promise,成功执行then,失败执行catch
web3.eth.getAccounts().then(function(result){
      console.log(result);
}).catch(function(err){
      console.log(err)
});

在开发过程中用上面的方式使用Ganache,可以避免每次都使用Ganache图形界面或者命令行来启动一个节点。

在工程中启动Ganache的Server

Ganache除了可以直接提供Provider之外,还可以作为一个HTTP Server,这样其他的一些服务或者应用就可以通过HTTP的方式调用对应的接口。使用非常简单,我们使用上面建立的工程,不过要添加一个依赖CircularJSON,执行下面的命令安装:

npm i circular-json -S

然后在工程目录下面创建一个server.js文件,内容如下:

// 引入fs模块,用于读写文件
const fs = require('fs');
// 引入ganache-cli用于提供测试服务
const ganache = require("ganache-cli");
// 引入circular-json用于格式化输出对象
const CircularJSON = require('circular-json');
var server = ganache.server();
// 监听8545端口
server.listen(8545, function(err, blockchain) {
      console.log(err);
    // console.log(blockchain)
    // fs.writeFileSync('blockchain.txt', CircularJSON.stringify(blockchain));
    // 输出ganache-cli中区块链数据结构及内容到blockchain文件中
      fs.writeFileSync('blockchain.txt', CircularJSON.stringify(blockchain, null, '\t'));
    // 打印钱包助记词
    console.log(blockchain.mnemonic);
});

启动服务器不需要web3.js,但是需要文件,所以引入Node.js的fs模块和circular-json将对象转换为字符串,因为对象中有循环引用,所以不能直接使用JSON,而是使用了CircularJSON。

上面设置监听端口8545,回调函数中我们打印了一下blockchain的助记词,当然也可以打印其他blockchain中的数据。blockchain的数据比较多,所以没有直接使用console输出,而是写入blockchain.txt文件中,多看这个文件有助于理解以太坊区块链数据结构。

因为数据比较多,这里就不一一给出blockchain的数据了,感兴趣可以自己动手试一试,然后看一下blockchain文件中的数据。

配置工程中依赖的Ganache

Ganache作为工程依赖的配置和命令行使用命令参数基本一致,以下为Ganache工程依赖常用的配置:

参数 说明
accounts 和命令行的--accounts相同
logger 实现了log方法的对象,例如console,用于输出日志
mnemonic 字符串,设置助记词
port 整数,设置端口
seed 字符串,设置种子
total_accounts 数字类型,账号数量
default_balance_ether 每一个生成账户,默认的以太坊数量
network_id 整数,网络ID
blocked boolean值,是否锁定账户
unlocked_accounts 数组,不锁定账户、地址或者索引值
db_path 区块数据存放位置
verbose 输出对象和返回对象

我们修改一下上面的例子:

// 引入fs模块,用于读写文件
const fs = require('fs');
// 引入ganache-cli用于提供测试服务
const ganache = require("ganache-cli");
// 引入circular-json用于格式化输出对象
const CircularJSON = require('circular-json');
let logOptions = {
      flags: 'a',
      encoding: 'utf8'
};
// 配置输出选项
let stdout = fs.createWriteStream('./stdout.log', logOptions);
// 配置输出日志
let logger = new console.Console(stdout);
let options = {
      // 设置日志输出器
      "logger": logger,
      // 设置助记词
      "mnemonic": "film bind rail critic diamond tourist aim audit master odor major rabbit",
      // 设置账户数量
      "total_accounts": 20,
      // 设置每一个账户的以太币
      "default_balance_ether": 200,
      // 设置数据库路径
      "db_path": "./db",
      "verbose": true
};
// 使用指定配置启动服务
var server = ganache.server(options);
// 监听8545端口
server.listen(8545, function(err, blockchain) {
      console.log(err);
    // console.log(blockchain)
    // fs.writeFileSync('blockchain.txt', CircularJSON.stringify(blockchain));
    // 输出ganache-cli中区块链数据结构及内容到blockchain文件中
      fs.writeFileSync('blockchain.txt', CircularJSON.stringify(blockchain, null, '\t'));
    // 打印钱包助记词
    console.log(blockchain.mnemonic);
});

上面的例子我们添加了一个options,配置了一个logger让日志输出到文件中,指定mnimonic来生成账户,生成账户的数量指定为20个,每一个账户的余额指定为200Ether,指定区块链数据存放位置为当前目录db,日志的输出为请求对象和返回对象。

对于Provider也一样,可以通过下面的方式创建:

ganache.provider(options);

Ganache交易相关的RPC方法

以太坊使用JSON-RPC协议用于提供RPC服务,现在使用的版本是JSON-RPC 2.0,Ganache也实现了这个协议。我们一般使用web3.js代码库来调用以太坊RPC接口,后续会单独介绍web3.js的使用,因此这里Ganache封装的RPC方法咱就不介绍了。

Truffle

刚开始使用Ganache的话,安装并启动图形界面就可以了,Ganache就绪后,让我开始Truffle。

安装truffle

npm install -g truffle

创建truffle工程

可以创建一个空项目模板,不过对于刚接触Truffle的同学,推荐使用Truffle Boxes,它提供了示例应用代码和项目模板。 我们将使用MetaCoin box作为案例,它创建一个可以在帐户之间转移的Token(代币)

mkdir MetaCoin
cd MetaCoin

下载 (“unbox”) MetaCoin box:

truffle unbox metacoin
如果要创建没有合约的空工程,可以使用 truffle init

如果出现Unbox failed则是因为无法访问境外域名导致,则修改/etc/hosts后重新执行即可:

# GitHub Start
192.30.255.112 gist.github.com
192.30.255.112 github.com
192.30.255.112 www.github.com
151.101.56.133 avatars0.githubusercontent.com
151.101.56.133 avatars1.githubusercontent.com
151.101.56.133 avatars2.githubusercontent.com
151.101.56.133 avatars3.githubusercontent.com
151.101.56.133 avatars4.githubusercontent.com
151.101.56.133 avatars5.githubusercontent.com
151.101.56.133 avatars6.githubusercontent.com
151.101.56.133 avatars7.githubusercontent.com
151.101.56.133 avatars8.githubusercontent.com
151.101.56.133 camo.githubusercontent.com
151.101.56.133 cloud.githubusercontent.com
151.101.56.133 gist.githubusercontent.com
151.101.56.133 marketplace-screenshots.githubusercontent.com
151.101.56.133 raw.githubusercontent.com
151.101.56.133 repository-images.githubusercontent.com
151.101.56.133 user-images.githubusercontent.com
# GitHub End

在操作完成之后,就有这样的一个项目目录结构:

以太坊实践整理(四)Truffle智能合约开发框架_第2张图片

可以修改truffle-config.js的networks.development配置truffle连接相应网络。

truffle工程默认连接本地7545端口私链,我们查看Ganache的设置-SERVER,看到私链端口是7545,所以无需修改网络。

MetaCoin.sol是核心代码,其他所有文件都是为该文件服务的,其他目录下的文件是为了配合该文件测试、编译和上传区块链平台的。

测试

在truffle工程目录下执行:

truffle test ./test/TestMetaCoin.sol

编译

truffle compile

编译成功后,工程目录下会增加build目录。该目录下生产的文件可以通过部署接口将智能合约部署到私链上。

部署

保持Ganache开启状态,执行以下命令:

truffle migrate

部署成功,此时Ganache账号和交易数据也有相应变化了。

交互

通过命令行与私链环境交互,将账户0的MetaCoin代币转账到账户1下:

// 进入truffle的JS命令行环境
MacBook:MetaCoin zhutx$ truffle console

// 获取合约实例
truffle(ganache)> let instance = await MetaCoin.deployed()
undefined

// 获取所有账户
truffle(ganache)> let accounts = await web3.eth.getAccounts()
undefined

// 查看所有账户
truffle(ganache)> web3.eth.getAccounts()
[
  '0xA8d9cFEB347D866dba06f057BAB791c575D60ab4',
  '0xED1612c169aBFC1602Af96267542deF3BA989c7d',
  '0x08Eacc0EDb63F6781460C2579567a6f48D0a3042',
  '0x3FaA53497EEfAa43941081D3FC387DA5De8C03E3',
  '0xE0b80dC83b1d1E7aD0AFD35C8c777410772d33c6',
  '0x9DE5598f54130D414379e3C7d758D9fF209FF1dc',
  '0x0A76F42Bd8F8DFC1725BB4B5C0a65E76F2AEE6E8',
  '0x51210c41bd217203CC8b345Da41FADcb3ba02033',
  '0x39413715881543AF4033007045a6DB14698f6C93',
  '0xB7dff62a168885976a9D49f924605fDfe7518515'
]

// 查看账户0下MetaCoin代币余额
truffle(ganache)> (await web3.eth.getAccounts())[0]
'0xA8d9cFEB347D866dba06f057BAB791c575D60ab4'
truffle(ganache)> let balance = await instance.getBalance(accounts[0])
undefined
truffle(ganache)> balance.toNumber()
10000

// 给账户1转移500个MetaCoin代币
truffle(ganache)> instance.sendCoin(accounts[1], 500)
{
  tx: '0x2117baf16bf016885f9081b5f3a2ebf9681af0c60df8ae8b2b84caa94a77fed9',
  receipt: {
    transactionHash: '0x2117baf16bf016885f9081b5f3a2ebf9681af0c60df8ae8b2b84caa94a77fed9',
    transactionIndex: 0,
    blockHash: '0x4eb868c2dbde65c5c9ea1c7d2530fb32ed5b737257b71a22066a80f29601a99d',
    blockNumber: 6,
    from: '0xa8d9cfeb347d866dba06f057bab791c575d60ab4',
    to: '0x2068728d1ca0b5f174a660131ceccccb16560305',
    gasUsed: 51520,
    cumulativeGasUsed: 51520,
    contractAddress: null,
    logs: [ [Object] ],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000200000000000000000000000000000000000001000000000000000000000000000000000000000000000200000000000000000000008000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000010000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000002000000000000000000020000000080000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: [ [Object] ]
  },
  logs: [
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x2117baf16bf016885f9081b5f3a2ebf9681af0c60df8ae8b2b84caa94a77fed9',
      blockHash: '0x4eb868c2dbde65c5c9ea1c7d2530fb32ed5b737257b71a22066a80f29601a99d',
      blockNumber: 6,
      address: '0x2068728D1Ca0b5F174A660131ceCccCB16560305',
      type: 'mined',
      id: 'log_77dfcf05',
      event: 'Transfer',
      args: [Result]
    }
  ]
}

// 查询账户1下的余额
truffle(ganache)> let received = await instance.getBalance(accounts[1])
undefined
truffle(ganache)> received.toNumber()
500

// 查询账户0下的余额
truffle(ganache)> let newBalance = await instance.getBalance(accounts[0])
undefined
truffle(ganache)> newBalance.toNumber()
9500

虽然上面的命令是在命令行下执行的,但与进行DAPP用户界面开发时创建vue前端工程引入Web3.js库去调用是一样的,本质都是调用Web3.js代码库,与以太坊发生交互。

你可能感兴趣的