跟着视频学习了以太坊相关的东西,遇到了一系列坑,今天整理成笔记。
一、安装nodejs
# 安装相关依赖
yum -y install git bzip2 gcc gcc-c++ ntp epel-release
# 源码安装
wget https://npm.taobao.org/mirrors/node/v11.0.0/node-v11.0.0.tar.gz
tar -zxvf node-v11.0.0.tar.gz
cd node-v11.0.0
./configure
make
make install
node -v # 检测是否安装成功
# 配置npm仓库
npm install -g cnpm --registry=https://registry.npm.taobao.org
二、安装geth
# 安装cmake
wget https://cmake.org/files/v3.15/cmake-3.15.2.tar.gz
tar zxvf cmake-3.15.2.tar.gz
mv cmake-3.15.2 /usr/local/
cd /usr/local/cmake-3.15.2
./bootstrap
gmake
gmake install
echo "export PATH=/usr/local/cmake-3.15.2/bin:$PATH" >> /etc/profile
source /etc/profile
cmake -version # 检测cmake是否安装成功
# 安装go语言环境
wget https://storage.googleapis.com/golang/go1.10.2.linux-amd64.tar.gz
tar -zxvf go1.10.2.linux-amd64.tar.gz
mv go1.10.2.linux-amd64 /usr/local/
echo "export GOROOT=/usr/local/go" >> /etc/profile
echo "export PATH=/usr/local/go/bin:$PATH" >> /etc/profile
source /etc/profile
go version # 检测go环境是否安装成功
# 安装以太坊,需要下载release版本的
cd /usr/local
git clone https://github.com/ethereum/go-ethereum.git # 一定要下载release版本的
cd go-ethereum/
make all
echo "export PATH=$PATH:/usr/local/go-ethereum/build/bin" >> /etc/profile
source /etc/profile
geth version # 检测geth是否安装成功
# 开启时间同步(使用chrony也可以)
systemctl enable ntpd
systemctl start ntpd
# 关闭防火墙,或者打开8087,30303端口
systemctl stop firewalld
systemctl disable firewalld
三、创建以太坊私有链
# 用于初始化链的配置文件
vim genesis.json
{
"config": {
"chainId": 15, # 私有链网络id不要和公开的链的id相同
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x2000", # 挖矿难度设置
"extraData" : "",
"gasLimit" : "0xffffffff", # 一个块中gas的上线
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"alloc": { } # “address”:{“balance”: “300000”} # 账户初始化wei个数,指定账户有这么多钱,账户并没有添加到链中
}
# 初始化私有链
geth --datadir dir init genesis.json # dir代表数据以及配置存放的位置
# 启动私有链
geth --datadir dir --networkid 15 # networkid要和genesis.json中的chainId相同
或者
geth --datadir dir --networkid 15 2>logdata.log # 启动私有链,将日志重定向到文件中
或者
geth --datadir dir --networkid 15 console 2>logdata.log # 进入控制台
或者
geth --datadir dir --networkid 15 --rpc --rpcport 8545 --rpcaddr "192.168.31.112" console 2>/srv/blocklogdata.log # --rpc表示启动远程调用,启动链的时候指定对应的端口和ip
# 以太坊提供了一条测试链,给定一个初始化的账户,默认有一定金额的以太币,并且这个账户一直处于挖矿状态
geth --datadir dir --dev --rpc --rpcport 8545 --rpcaddr "192.168.31.112" console 2>/srv/blocklogdata.log # --dev就表明了启动的是测试链,不需要再指定networkid
四、安装solcjs
# solcjs是solidity的编译器,安装solcjs需要nodejs的支持
npm install -g solc # 安装默认版本,我这里是0.6.2
# 在solidity中应该注意编译器的版本,需要小于等于0.6.2
pragma solidity >=0.4.22 <0.6.3
五、编写合约
# 这里是一个简单的投票合约
vim Voting.sol
pragma solidity >=0.4.22 <0.6.3;
contract Voting {
bytes32[] public candidateList; //候选人列表
mapping(bytes32=>uint8) public votesReceived; //候选人=>得票数
constructor(bytes32[] memory _candidateList) public { // 构造函数,参数是一个数组
candidateList = _candidateList;
}
function totalVotesFor(bytes32 _candidate) view public returns(uint8){ // 获取票数
require(validCandidate(_candidate));
return votesReceived[_candidate];
}
function voteForCandidate(bytes32 _candidate) public { // 投票
require(validCandidate(_candidate));
votesReceived[_candidate] += 1;
}
function validCandidate(bytes32 _candidate) view public returns(bool){ // 是否在候选人列表
for(uint i=0;i<candidateList.length;i++ ){
if(candidateList[i] == _candidate){
return true;
}
}
return false;
}
}
六、编译合约
solcjs --bin --abi Voting.sol
# 得到如下两个文件
Voting_sol_Voting.abi # 接口,json格式
Voting_sol_Voting.bin # 字节码文件
七、启用node部署合约
# 创建一个目录并且初始化(不同的目录可以安装不同版本的模块)
mkdir project
cd project
npm init
# 安装web3模块
npm install web3@0.20.1 # 这里使用的是0.20.1版本的web3
# 为了方便测试,这里使用dev测试链
geth --datadir dir --dev --rpc --rpcport 8545 --rpcaddr "192.168.31.112" console 2>/srv/blocklogdata.log # 开启测试链,指定ip和端口
# 进入node交互页面
node
# 获取web3对象
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new Web3.providers.HttpProvider("http://192.168.31.112:8545"));
# 创建抽象合约
var _abi = JSON.parse(将Voting_sol_Voting.abi的内容复制到这里);
var _bin = '0x' + 将Voting_sol_Voting.bin的内容复制到这里;
var myContract = web3.eth.contract(_abi); # 抽象合约
# 部署合约是通过交易完成的,因此需要创建一个交易的参数
var _option = {from: web3.eth.accounts[0], data: _bin, gas: 2000000}; # 合约部署者,字节码,gas值
# 创建合约实例(部署合约)
var contractInstance = myContract.new(['alex', 'jack', 'jason', 'davie', 'tom', 'ajax', 'spi'], _option ); # 构造函数参数在前(允许有多个用逗号分隔),交易参数在最后
# 由于合约的构造函数是有参数的,因此需要指定参数。
# 在合约中构造函数的类型是bytes32[],这里传入的是string[], 会自动将string转为bytes32,在remix中不会自动转换
# 实例创建完毕之后就得到了一个合约的地址
# 这里得到的合约地址是0xb286332ae0d181103db3a4d3412b4750b6e9c30d
八、调用合约函数
# 通过合约地址得到合约实例
var contractInstance = myContract.at(‘0xb286332ae0d181103db3a4d3412b4750b6e9c30d’);
# 调用合约函数的三种方法
1、合约实例.函数名(param1, param2, ..);
contractInstance.voteForCandidate('davie', {from: web3.eth.accounts[0]});
# {from: web3.eth.accounts[0]} 是交易时的参数,类似于上面定义的_option
# 这种方式可以自动判断是 `纯粹的函数调用(call)` 或 `以发送交易的方式调用函数(sendTransaction)`
2、合约实例.函数名.sendTransaction(param1, param2, ..);
contractInstance.voteForCandidate.sendTransaction('davie', {from: web3.eth.accounts[0]});
# 这种方式是以 `发送交易` 的方式调用函数
3、合约实例.函数名.call(param1, param2, ..);
contractInstance.totalVotesFor.call('davie').toString();
# 这种方式是 `纯粹的函数调用`
# 当状态变量(合约中的属性)发生改变的时候,用发送交易的方式调用函数。
如有错误欢迎留言指正。