以太坊已经发展了很多年了,和以太坊配套的开发环境版本很多,初次学习很容易出现版本不兼容的问题,很不适合新手,本文的主要内容就是介绍如何搭建一套完整的以太坊开发环境,节省开发者宝贵时间。
Ubuntu版本:20.04
Ethereum版本:1.9.23
Nodejs版本:v10.19.0
Solidity版本:0.7.4
Truffle版本:v5.1.52
众所周知,以太坊(Ethereum)是一个分布式账本,在以太坊上记账的内容具备分布式、不可篡改、追溯等特性,这也是区块链之所以流行的主要原因。
以太坊构建了一个虚拟机EVM,在虚拟机中可以部署图灵完备的智能合约(Smart Contract),所谓图灵完备,就是在合约中可以执行加减乘除等逻辑运算,可以实现现有计算机编程语言的所有逻辑,智能合约执行的每一步骤都被记录在以太坊中,可以被追溯,所以智能合约的研究也非常火热。目前比较流行的智能合约编程语言叫做Solidity,编译器也叫Solidity,Solidity编译器将用户编写好的智能合约编译成二进制(类似于汇编语言),以太坊提供将二进制部署到虚拟机的接口,用户可以手动将编译后的二进制部署到以太坊上进行执行。
由于编译、部署、调用Solidity的工作比较重复,需要一种成熟的框架来帮助开发者节省时间,Truffle应运而生,官方介绍Truffle是一个世界级的开发环境,测试框架,以太坊的资源管理通道,致力于让以太坊上的开发变得简单。
那么接下来的内容就介绍如何快速搭建以上环境,从而快速进入开发阶段。Let’s Rock!
接下来介绍以太坊、Solidity编译器、Truffle等开发环境的搭建方法。
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum
使用如下命令查看Solidity版本
geth version
安装Nodejs命令如下:
sudo apt-get install -y nodejs
查看Nodejs版本命令如下:
nodejs -v
安装命令如下:
sudo apt-get install npm
sudo npm install solc
solc --version
安装命令如下所示:
npm install -g truffle
truffle version
以上开发环境搭建完成后,就可以编写代码进行开发了。下面的内容就介绍程序开发的步骤。
创建私链需要定义创世块文件,这个文件用来创建区块链的创世区块。新建blockchain/consortium_blockchain文件夹
在文件夹下新建genesis.json文件
mkdir blockchain
cd blockchain
mkdir consortium_blockchain
cd consortium_blockchain
vim genesis.json
写入如下内容:
{
"config": {
"chainId": 8434,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"byzantiumBlock": 4,
"alien": {
"period": 2,
"epoch": 300,
"maxSignersCount": 5,
"minVoterBalance": 100000000000000000000,
"genesisTimestamp": 1536136198,
"signers": [
"0x393faea80893ba357db03c03ee73ad3e31257469",
"0x30d342865deef24ac6b3ec2f3f8dba5109351571",
"0xd410f95ede1d2da66b1870ac671cc18b66a97778"
]
}
},
"nonce": "0x0",
"timestamp": "0x5b8f92c2",
"extraData": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"cbfc29c31a31c869f9eb59a084d9019965978a7e": {
"balance": "0x31d450f18af132720000000"
},
"393faea80893ba357db03c03ee73ad3e31257469": {
"balance": "0xd3c21bcecceda1000000"
},
"30d342865deef24ac6b3ec2f3f8dba5109351571": {
"balance": "0xd3c21bcecceda1000000"
},
"d410f95ede1d2da66b1870ac671cc18b66a97778": {
"balance": "0xd3c21bcecceda1000000"
},
"a25dc63609ea7ea999033e062f2ace42231c0b69": {
"balance": "0xd3c21bcecceda1000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
执行下面命令初始化创世块
geth --datadir "./myblockchain" init genesis.json
执行结果如下:
初始化结束后,当前目录会新建目录myblockchain,myblockchain目录下多了geth和keystore两个文件夹:
1) geth保存的该链上的区块数据
2)keystore保存该链上的用户信息
执行如下命令查看目录结构:
sudo apt install tree
tree myblockchain
geth --identity "myethereum" --rpc --rpcaddr "127.0.0.1" --port 30304 --rpcport "7545" --rpccorsdomain "*" --datadir ./myblockchain --rpcapi "db,eth,net,web3,admin,personal" --networkid 5777 console --allow-insecure-unlock
这里先介绍一下一些参数:
参数名 | 描述 |
---|---|
identity | 区块链的标识,用于标识目前网络的名字 |
init | 指定创世块文件的位置,并创建创世块 |
datadir | 当前区块链数据存放的位置 |
port | 网络监听端口,默认30303 |
rpc | 启动rpc通信 |
rpcapi | 设置允许rpc的客户端,一般为db,eth,net,web3 |
rpccorsdomain | 指定什么url能连接到你的节点执行rpc定制端任务,如果输入是“*”,则任何url都可以连接到你的rpc实例。 |
rpcaddr | 连接rpc的地址,默认为localhost,为了方便访问建议使用本机IP地址 |
rpcport | 连接rpc的端口,默认为8545 |
networkid | 设置当前区块链的网络ID,用于区分不同的网络,是一个数字,公链为1;这个和创世块文件的chainid没有直接关联 |
console | 启动命令行模式,可以再geth中执行命令 |
屏幕输出:
4. 连接第二个geth终端
为了方便使用,经常要打开多个终端连接到同一个私链节点,可以采用如下命令,其中ipc就是启动私链是输出的“ipc endpoint opened”
cd blockchain/consortium_blockchain/myblockchain
geth attach geth.ipc
运行效果图如下所示:
5. geth创建账号、启动挖矿、停止挖矿
在geth终端就可以执行启动挖矿、创建账户等操作,初始化后系统无账号,需要创建新账号,才能启动挖矿:
eth.accounts
新建账户:
personal.newAccount("12345678");
eth.getBalance(eth.accounts[0])
miner.start()
停止挖矿:
miner.stop()
personal.unlockAccount(eth.accounts[0])
交易池状态:
txpool.status
区块总数:
eth.blockNumber
获取交易:
eth.getTransaction("0x5cff7cd36fe841a7eb35ba30f99c8851a4e57715e4b648aeb341c3c2619879cd");
获取第421个区块的信息:
eth.getBlock(421);
连接到其他节点:
admin.addPeer();
设置挖矿账户:
miner.setEtherbase(eth.coinbase)
转账交易,要先解锁账户:
eth.sendTransaction({
from:"0xe76c73baf75a67dd9637ad2da33d13288d73ee16",to:"0x62232534fda9ffe0c5f32cf6d48568a861065fea",value:web3.toWei(100,"ether")})
truffle init
初始化一个空项目,会生成三个文件夹 contracts(包含Migrations.sol,之后得将要部署的合约放在里面)、migrations(包含1_initial_migration.js,truffle migrate会执行该js)、test(空文件夹),以及两个js配置文件(一个win,一个mac/linux,与truffle部署网络相关)
truffle develop
开启truffle自带的一个以太坊环境并创建10个账户,带有余额,console里面调用web3接口。web3.js是以太坊去中心化应用DApp必备的JavaScript库,提供了与Geth通信的JavaScript API(https://github.com/ethereum/wiki/wiki/javascript-API),使用JSON-RPC协议与Geth通信。以太坊只提供了JSON-RPC接口,而web.js是一个封装了JSON-RPC的API库(https://github.com/ethereum/wiki/wiki/json-rpc 直接调用不方便)
3. 编译合约
truffle compile
truffle migrate
重复部署需要执行
truffle migrate --reset
mkdir truffletest
cd truffletest
truffle init
contracts/: 智能合约存放的目录,默认情况下已经帮你创建 Migrations.sol合约。
migrations/: 存放部署脚本
test/: 存放测试脚本
truffle-config.js: truffle的配置文件
3. 合约编译
demo的合约代码存储在contracts目录下,编译该合约代码的指令
truffle compile
编译成功的结果如下图:
如合约改动,truffle compile仅对改动的合约进行编译,如需强制编译整体合约,可使用 truffle compile --compile-all
4. 测试代码编写
在contract目录下新建Greeter.sol合约文件,如下所示:
pragma solidity >=0.4.16 <0.8.0;
contract Greeter
{
function helloWorld() public returns (string memory) {
return "111111Hello, World!";
}
function myhelloWorld() public returns (string memory) {
return "Hello, World!ss";
}
}
修改migrations/1_initial_migration.js文件,如下所示:
const Migrations = artifacts.require("Migrations");
const Greeter = artifacts.require("Greeter");#新增代码
module.exports = function (deployer) {
deployer.deploy(Migrations);
deployer.deploy(Greeter);#新增代码
};
重新编译合约,如下所示:
5. 部署合约
执行如下命令进行合约部署:
truffle migrate
personal.unlockAccount(eth.accounts[0])
再次部署,如下图:
合约部署成功,在以太坊命令行可以看到一个合约正在队列中:
miner.start()开始挖矿。
合约部署成功图如下所示:
也有可能gas耗费完了,如下图:
这是因为账户的以太坊不够,需要持续挖矿一段时间。
truffle console
> var contract;
undefined
> Greeter.deployed().then(function(instance){
contract= instance;});
undefined
> contract.helloWorld()
注意:如果虚拟机性能偏低,很容易出现gas消耗完毕,合约部署不成功的问题,解决方法是使用ganache-cli工具,具体方法如下:
sudo npm install -g ganache-cli
安装成功如下图:
2. 运行ganache-cli
命令行执行
ganache-cli
truffle migrate
ss@ubuntu:~/blockchain/truffletest$ truffle console
> var contract;
undefined
> Greeter.deployed().then(function(instance){
contract= instance;});
undefined
> contract.helloWorld()
> contract.helloWorld.call()
注意:
当使用truffle test测试合约的时候,需要使用web3通信,需要安装web3,安装方法如下:
sudo npm install web3@^0.20.0
双击打开
点击quickstart或者new workspace
设置端口port和networkid,如下图:
点击save workspace,如图:
然后执行truffle工程里面的truffle-config.js文件,修改端口为对应的端口,例子中是9545.
就可以执行truffle migrate或者truffle console命令了,如下图:
也可以使用geth attach这个区块链,命令如下:
geth attach http://127.0.0.1:9545
如下图:
以上的以太坊只开了一个节点,还无法模拟多个节点的协作的目的,下面就介绍怎么搭建一个多节点的私有链。要在私有网络中建立多个node组成的集群,并互相发现,产生交易。
为了在本地网络运行多个以太坊节点的实例,必须确保一下几点:
每个实例都有独立的数据目录(–datadir)
每个实例运行都有独立的端口.(eth和rpc两者都是)(–port 和 --rpcprot)
在集群的情况下, 实例之间都必须要知道彼此.
唯一的ipc通信端点,或者禁用ipc.
启动第一个节点(指定端口),运行命令和结果如下:
admin.nodeInfo.enode
如下所示:
上面的命令以命令行的(console)的方式启动了节点, 所以我们可以通过继续输入下面的命令获取节点实例的enode url。
2. 新建另一个节点
再打开一个终端,初始化第二个节点:
geth --datadir "./myblockchain1" init genesis.json
geth --rpcaddr "127.0.0.1" --datadir ./myblockchain1 --rpcapi "db,eth,net,web3,admin,personal" --networkid 5777 --port 61911 --rpcport "7546" --bootnodes "enode://88017819fba64041dbff876e16c3dc4df96ef42ba194e2a7c127999501994da08db824a2a9200eca7d38d713a3a15ecc70ef9c3c30384b47688a42d943af14ac@127.0.0.1:30303?discport=52408" console
上面的命令中,–bootndoes 是设置当前节点启动后,直接通过设置–bootndoes 的值来链接第一个节点, --bootnoedes 的值可以通过在第一个节的命令行中,输入:admin.nodeInfo.enode命令打印出来.
也可以不设置 --bootnodes, 直接启动,启动后进入命令行, 通过命令admin.addPeer(enodeUrlOfFirst Instance)把它作为一个peer添加进来.
为了确认链接成功,第二个节点输入:
admin.nodeInfo
admin.addPeer("enode:公有链地址@IP地址:30303")
第一个节点输入:
net.peerCount
admin.peers
从得到的结果可以看出,第一个节点有1个peer链接, 链接的node id为:
“88017819fba64041dbff876e16c3dc4df96ef42ba194e2a7c127999501994da08db824a2a9200eca7d38d713a3a15ecc70ef9c3c30384b47688a42d943af14ac”
这个id,正好就是第二个节点的id.
按照这样的方式继续扩展,可以非常容易就可以建立本地节点集群.这些工作都可以写成脚本代码来完成, 里面还可以包含创建账户,挖矿等…
请参考:https://github.com/ethersphere/eth-utils下的gethcluster.sh脚本,以及README中的使用方法和示例.
链接成功后,使用我们在上一篇文章中挖矿的账户,向第二个节点发送 “ether”(以太币的货币单位,还有一种叫"Wei",基本上这些货币单位都是用一些牛逼的人的名字来命名的).
首先查看第二个节点的Wei数量和整个网络的区块号,还有接收货币的账号id:
> eth.getBalance(eth.accounts[0])
138000000000000000000
> eth.blockNumber
44
> eth.accounts[0]
"0xde6dbf1967bf7a848f22862f1343a720647fc3fa"
>
在第一个节点命令行中,执行下面的操作:
> personal.unlockAccount(eth.accounts[0], "password")
true
> eth.sendTransaction({
from: "0xde6dbf1967bf7a848f22862f1343a720647fc3fa", to: "0x3d9c81058d17ba699568241649912c60d7083f9d", value: web3.toWei(1, "ether")})
"0x7222e34a5d7ff6fe4f932a5703b66fa42d34b3b4825d0d16993dfe8bc3eef220"
>
eth.sendTransaction就是执行发送以太币的操作, 参数from, to分别是发送账户和接收账户, web3.toWei(1, “ether”)是将1单位"ether"转换为相应的"Wei"数量.
然后执行挖矿(这里我也不理解,为什么发送货币以后,要通过挖矿才能让交易生效)。