在之前内容的学习中,我们发现compile、deploy等文件在不同的项目中是可以互用的,根据低聚合高耦合的代码设计规则,我们可以将此部分封装成一个框架,从而更好的完成开发。所以ETH官方推出了Truffle框架。那么,Truffle到底为我们带来了什么呢?让我们来一探究竟。
特殊要求:请将node版本降低到11.15.X版本,node12、13版本会因为不兼容报错,博主在这上面花了2个小时才解决。
1.安装truffle
npm install truffle -g
2.官方文档地址
github:
https://github.com/trufflesuite/truffle
官方文档:
https://www.trufflesuite.com/docs/truffle/overview
$ truffle
Truffle v3.4.11 - a development framework for Ethereum
$ cd myproject
$ truffle init
ps:
1.由于某些原因在安装过程中,可能会卡着不动,这时候您需要科学上网或者使用淘宝镜像cnpm安装
truffle-config.js是win端的设置,truffle.js是mac或linux的设置。在创建工程后,windows端需要删除truffle.js,不然可能会报错,linux或mac端不用管。
1.在contracts中创建Simple.sol文件,并写入。文件名需和合约名一致
pragma solidity ^0.4.24;
contract Simple{
uint256 tmp;
function setValue(uint256 value)public {
tmp=value;
}
function getValue()public view returns(uint256){
return tmp;
}
}
2.执行编译命令
truffle compile
3.在migrations文件夹中创建2_Simple_migrations.js文件并写入
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
4.设置truffle-config.js、修改默认文件为:
module.exports = {
networks: {
ganacheNetwork: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
}
};
//修改编译器
module.exports = {
compilers: {
solc: {
version: , // A version or constraint - Ex. "^0.5.0"
// Can also be set to "native" to use a native solc
docker: , // Use a version obtained through docker
parser: "solcjs", // Leverages solc-js purely for speedy parsing
settings: {
optimizer: {
enabled: ,
runs: // Optimize for how many times you intend to run the code
},
evmVersion: // Default: "petersburg"
}
}
}
}
4.连接ganache后执行部署命令
truffle migrate --network ganacheNetwork
从上述例子中,我们可以发现truffle的第一个优势:1.深度集成。开发,测试,部署一行命令都可以搞定。
在truffle中默认提供了一个与以太坊节点仿真器工具develop,将数据存储在了内存中,它可以更加快速的测试,但看不到数据。默认端口为9545,启动命令为truffle develop ,在启动后直接执行compile进行编译,直接执行migrate进行部署。
1.develop与合约交互
var deployinstance;
Test01.deployed().then(instance=>{deployinstance=instance})
//输入deployinstance ,这时返回值不是未定义,而是有合约实例了
deployinstance
//与合约进行交互。格式如:deployinstance.getValue.call({from:xxx}).then()
deployinstance.getValue.call()
//交互,格式如:deployinstance.setValue(500,{from:xxx}).then(),from可省略,默认为第一个账户
deployinstance.setValue(500)
2.合约测试Test
由于博主truffle版本问题,在本地运行报错,错误原因为:"before all" hook: prepare suite。这是由于truffle版本的问题,在truffle4中不会遇见此问题,测试代码如下:
import 'truffle/Assert.sol';
import 'truffle/DeployedAddresses.sol';
import '../contracts/Test01.sol';
contract TestTest01{
//合约大写,函数名小写
function testSet() public {
//Test01 ss=Test01(DeployedAddresses.Test01());
Test01 ss=new Test01();
ss.setValue(500);
uint256 res=ss.getValue();
Assert.equal(res,500,"res should be 500,error");
}
}
3.重新部署合约
migrate --reset
若还是失败,则手动删除build文件夹后,重新compile
从上述例子中,我们可以发现truffle的第二个优势:2.提供了控制台,使用框架构建后,可以直接在命令行调用输出结果,可极大方便开发调试
1.安装
新建一个文件夹,进入该文件夹内使用命令 truffle unbox react
命令下载该框架。
安装过程错误处理
1.1由于该框架有230多M,所以需要耐心等待,如果下载过程出错了,请删掉原文件夹后重新下载。
1.2若安装过程中,出现node-gyp错误,请将本地python版本降低至2.7.11,再将本地vs装成visual studio 2015,博主在这里搞了一下午。若您需要python2和python3共存,请将环境变量重新设置。
2.启动项目
进入项目文件后,cd client后 输入npm start,即可启动项目
3.运行合约实例、以及源码解析
由于原项目本身附带了一个SimpileStorage.sol的合约,所以我只需对这个合约进行研究。首先进入develop模式后进行compile和migrate,这时web页面会出现界面,并访问metamask将value修改为5,若有界面显示value却没有修改,则需要在metamask中设置一个随机的ChainID。项目部分源码解析如下:(App.js)
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json"; //第一步引入合约
import getWeb3 from "./getWeb3";
import "./App.css";
class App extends Component {
state = { storageValue: 7, web3: null, accounts: null, contract: null };
componentDidMount = async () => {
try {
// Get network provider and web3 instance.
const web3 = await getWeb3(); //第二步创建web3对象
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId(); //第三步返回当前网络ID
//第四步,拿到网络对象
const deployedNetwork = SimpleStorageContract.networks[networkId];
//第五步获取实例
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,//ABI
deployedNetwork && deployedNetwork.address,//合约地址
);
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ web3, accounts, contract: instance }, this.runExample);
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
runExample = async () => {
const { accounts, contract } = this.state;
// Stores a given value, 5 by default.
await contract.methods.set(5).send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
console.log(response,"oooooooooooooo")
// Update state with the result.
this.setState({ storageValue: response });
};
render() {
if (!this.state.web3) {
return Loading Web3, accounts, and contract...;
}
return (
Good to Go!
Your Truffle Box is installed and ready.
Smart Contract Example
If your contracts compiled and migrated successfully, below will show
a stored value of 5 (by default).
Try changing the value stored on line 40 of App.js.
The stored value is: {this.state.storageValue}
);
}
}
export default App;
let Web3 = require('web3')
let web3 = new Web3('http://127.0.0.1:7545')
console.log('version :', web3.version)
//获取账户
let accounts
//error first callback style
//旧版本web3只支持方式一形式,不支持方式二方式三
web3.eth.getAccounts((err, res) => {
console.log('获取账户方式一:回调函数形式')
if (err) {
console.log('err:', err)
}
// console.log(res)
})
web3.eth.getAccounts().then(res => {
console.log('获取账户方式二:then形式')
// console.log(res)
}).catch(e => {
console.log(e)
})
let f = async () => {
try {
let accounts = await web3.eth.getAccounts()
console.log('获取账户方式三:async/await形式')
// console.log(accounts)
let balance1 = await web3.eth.getBalance(accounts[0])
console.log('balance1:', balance1)
//balance1: Promise { }
// let balance1 = web3.eth.getBalance(accounts[0])
let defaultAccount = web3.eth.defaultAccount
console.log('default:', defaultAccount)
//设置默认账号
web3.eth.defaultAccount = accounts[2]
console.log('new default:', web3.eth.defaultAccount)
let defaultBlock = web3.eth.defaultBlock
console.log('defaultBlock:', defaultBlock)
//由账户0向账户1转10eth
let res = await web3.eth.sendTransaction({
// from: accounts[0], //如果不指定from,那么会使用defaultAccount的值
to: accounts[1],
gas: '6000000',
value: web3.utils.toWei('10', 'ether'),
})
//修改defaultBlock
// web3.eth.defaultBlock = 3
web3.eth.defaultBlock = 'latest'
console.log('defaultBlock:', web3.eth.defaultBlock)
let balance2 = await web3.eth.getBalance(accounts[2])
console.log('balance2:', balance2)
} catch (e) {
console.log(e)
}
}
f()
由于js处理大数据会导致精度缺失,我们可以在区块链计算中引入Bignumber模块,或使用web3.utils中的toBN模块。在两个方法后结果都是采用的科学技术法的数值,其中符号: s 指数: e 尾数: c,为了更好的观察数据,这里我们只需用toString()转换一下即可。
6.1Bignumber
安装
npm i bignumber.js
处理加法
let BigNumber=require('bignumber.js');
let x=new BigNumber('10000000000000000000000000000000000000000050');
let y=new BigNumber('10');
let res=x.plus(y);
console.log(res.toString());
//- 详细示例
var BigNumber = require('bignumber.js');
console.log('====相等?====')
x = new BigNumber(123.4567);
y = new BigNumber(123456.7e-3);
z = new BigNumber(x);
console.log(x.eq(y))
console.log('====加法====')
m = new BigNumber(10101, 2);
n = new BigNumber("ABCD", 16); //16代表进制
console.log(m.plus(n))
console.log(m.plus(n).toString())
console.log('====减法====')
x = new BigNumber(0.5)
y = new BigNumber(0.4)
console.log(0.5 - 0.4)
console.log(x.minus(y).toString())
console.log('====乘法====')
x = new BigNumber('2222222222222222222222222222222222')
y = new BigNumber('7777777777777777777777777777777777', 16)
console.log(x.times(y).toString())
console.log('====除法====')
console.log(x.div(y).toString())
console.log(x.div(y).toFixed(6).toString()) //6代表小数位
console.log('==== x = -123.456====')
x = new BigNumber(-123.456)
console.log(x)
console.log("尾数x.c:",x.c)
console.log("指数x.e:",x.e)
console.log("符号x.s",x.s)
6.2web3.utils.toBN()
let Web3=require('web3');
let web3=new Web3();
let x=web3.utils.toBN('100000000000000000000000000050');
let y=web3.utils.toBN('10');
let res=x.add(y);
console.log(res.toString());
6.3 单位转换
var Web3 = require('Web3')
var web3 = new Web3()
console.log('\n将wei转换为ether, Gwei, Mwei')
console.log(web3.utils.fromWei('12345567890876433', 'ether'))
console.log(web3.utils.fromWei('12345567890876433', 'Gwei'))
console.log(web3.utils.fromWei('12345567890876433', 'Mwei'))
console.log('\n转换为Wei')
console.log(web3.utils.toWei('1', 'ether'))
console.log(web3.utils.toWei('1', 'Gwei'))
console.log(web3.utils.toWei('1', 'Mwei'))
6.4转换为十六进制
var Web3 = require('Web3')
var web3 = new Web3()
console.log(web3.utils.toHex('a'))
console.log(web3.utils.toHex(1234))
console.log(web3.utils.toHex({name:'Duke'}))
//将所有传入的数据都当做字符串进行处理,然后按照ASCII的16进制返回
//如果内部有单引号,则自动转化成双引号,再在外部用单引号括起来
console.log(JSON.stringify({name:'Duke'}))
console.log(web3.utils.toHex('{"name":"Duke"}'))
console.log(web3.utils.toHex(JSON.stringify({name:'Duke'})))
console.log(web3.utils.toHex([1,2,3,4]))
console.log(web3.utils.toHex('[1,2,3,4]'))
//执行结果
0x61
0x4d2
0x7b226e616d65223a2244756b65227d
{"name":"Duke"}
0x7b226e616d65223a2244756b65227d
0x7b226e616d65223a2244756b65227d
0x5b312c322c332c345d
0x5b312c322c332c345d
十六进制与ASCII转换
var Web3 = require('Web3')
var web3 = new Web3()
console.log("先将字符串'xyz'转换为ascii,然后转化为十六进制")
var str = web3.utils.fromAscii('xyz')
console.log(str)
console.log("先将十六进制转换为ascii,然后转化为字符串")
str = web3.utils.toAscii('0x78797a')
console.log(str)
生成hash字符串
var Web3 = require('Web3')
var web3 = new Web3()
var hash0 = web3.utils.sha3('abc')
console.log(hash0)
//对结果0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45进行hash
var hash1 = (web3.utils.sha3(hash0))
console.log(hash1)
{
"alloc": {},
"config": {
"chainID": 72,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x0000000000000000",
"difficulty": "0x4000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit": "0xffffffff"
}
mkdir myPrivateNet
cd myPrivateNet
mv genesis.json ./ //<--这个genesis.json放到这里是便于管理,位置不限,能访问即可
==//init 务必要加上,否则可以创建成功,但是后面有问题==
geth --datadir "./node1" init genesis.json
执行效果
[duke ~/ethStudy/myPrivateNet]$ geth --datadir "./node1" init genesis.json
INFO [07-16|07:49:26] Maximum peer count ETH=25 LES=0 total=25
INFO [07-16|07:49:26] Allocated cache and file handles database=/Users/duke/ethStudy/myPrivateNet/node1/geth/chaindata cache=16 handles=16
INFO [07-16|07:49:26] Writing custom genesis block
INFO [07-16|07:49:26] Persisted trie from memory database nodes=0 size=0.00B time=8.302µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-16|07:49:26] Successfully wrote genesis state database=chaindata hash=942f59…a2588a
INFO [07-16|07:49:26] Allocated cache and file handles database=/Users/duke/ethStudy/myPrivateNet/node1/geth/lightchaindata cache=16 handles=16
INFO [07-16|07:49:26] Writing custom genesis block
INFO [07-16|07:49:26] Persisted trie from memory database nodes=0 size=0.00B time=1.506µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-16|07:49:26] Successfully wrote genesis state database=lightchaindata hash=942f59…a2588a
geth --datadir "./node1" --networkid 72 --port 30301 console
--datadir: 指定节点数据存储路径,此处会自动创建 node1
,之所以这样命名,是因为后面会创建node2
,便于模拟多个节点交互
--networkid: 当前网络的id,写在genesis.json中的
--port 端口
console: 表明同时启动一个交互的终端(非必要)
这个关键字可以省略,省略的话表明只启动节点服务,不创建交互终端,后面如果想接入这个节点的话,可以通过attach命令接入,后面讲。
效果:
[duke ~/ethStudy/myPrivateNet]$ geth --datadir "./node1" --networkid 72 --port 30301 console
INFO [07-16|07:49:59] Maximum peer count ETH=25 LES=0 total=25
INFO [07-16|07:49:59] Starting peer-to-peer node instance=Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
INFO [07-16|07:49:59] Allocated cache and file handles database=/Users/duke/ethStudy/myPrivateNet/node1/geth/chaindata cache=768 handles=128
INFO [07-16|07:49:59] Initialised chain configuration config="{ChainID: 72 Homestead: 0 DAO: DAOSupport: false EIP150: EIP155: 0 EIP158: 0 Byzantium: Constantinople: Engine: unknown}"
INFO [07-16|07:49:59] Disk storage enabled for ethash caches dir=/Users/duke/ethStudy/myPrivateNet/node1/geth/ethash count=3
INFO [07-16|07:49:59] Disk storage enabled for ethash DAGs dir=/Users/duke/.ethash count=2
INFO [07-16|07:49:59] Initialising Ethereum protocol versions="[63 62]" network=72
INFO [07-16|07:49:59] Loaded most recent local header number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Loaded most recent local full block number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Loaded most recent local fast block number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Regenerated local transaction journal transactions=0 accounts=0
INFO [07-16|07:49:59] Starting P2P networking
INFO [07-16|07:50:02] UDP listener up self=enode://58e6eec1f00e0b2c221053c580c5f9c5b482bac142c97e711200e1709a736a5c4b9751d5637de524858b208bad7f3ecc6d17c36065801be438ee3b00fe931e35@192.168.1.65:30301
INFO [07-16|07:50:02] RLPx listener up self=enode://58e6eec1f00e0b2c221053c580c5f9c5b482bac142c97e711200e1709a736a5c4b9751d5637de524858b208bad7f3ecc6d17c36065801be438ee3b00fe931e35@192.168.1.65:30301
INFO [07-16|07:50:02] IPC endpoint opened url=/Users/duke/ethStudy/myPrivateNet/node1/geth.ipc
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> INFO [07-16|07:50:02] Mapped network port proto=tcp extport=30301 intport=30301 interface="UPNP IGDv1-IP1"
>
>
[duke ~/ethStudy/myPrivateNet]$ tree node1 -d
node1
├── geth
│ ├── chaindata
│ ├── lightchaindata
│ └── nodes
└── keystore
5 directories
至此,私有网络已搭建完成,下面开始执行各项命令
instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> eth.accounts
[] <--当前网络账户为空
>
注,,我们在创世块中可以预先指定一些账户,在alloc字段
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": {
"balance": "300000"
},
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": {
"balance": "400000"
}
}
>
> personal.newAccount("1111")
"0xe5c9766404eb3633163c85ef13e7a1f79cdee922"
>
>
> eth.accounts
["0xe5c9766404eb3633163c85ef13e7a1f79cdee922"]
>
其中,"1111"是这个账户的密码
> personal.newAccount("111")
"0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8"
> personal.newAccount("111")
"0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f"
> personal.newAccount("111")
"0xf3b1e1712a311443db772c55e12c9634912199ab"
> personal.newAccount("111")
"0xef89f85b7e276dcf738276df88888298cd9272ee"
>
> eth.coinbase
INFO [07-16|07:54:10] Etherbase automatically configured address=0x5032641c3f09bc8995a60C485B9Ca4ac3d073fD8
"0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8"
>
可以看到,默认的矿工是第一个账户,可以通过eth.accounts来查看当前节点的所有账户,为了方便记忆,我们把所有的账户密码都设置为”1111“。
查看一下keystore目录,可以看到创建了四个账户文件
[duke ~/ethStudy/myPrivateNet]$ ls node1/keystore/
UTC--2018-07-15T23-52-31.825281356Z--5032641c3f09bc8995a60c485b9ca4ac3d073fd8
UTC--2018-07-15T23-53-42.431125900Z--864ced8aff4b883c99ab1c5156f2d51c47b25f8f
UTC--2018-07-15T23-53-44.166684858Z--f3b1e1712a311443db772c55e12c9634912199ab
UTC--2018-07-15T23-53-45.567447952Z--ef89f85b7e276dcf738276df88888298cd9272ee
[duke ~/ethStudy/myPrivateNet]$
> miner.setEtherbase(eth.accounts[1])
true
> eth.coinbase
"0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f"
>
可以看到coinbase的值已经改变,挖矿的时候,奖励会发给指定的账户。
==注,每次节点重新启动时,会自动将coinbase设置为第一个账户。==
私链重启后,挖矿人重新自动恢复为第一个主账户
> eth.getBalance(eth.accounts[0])
0
> eth.getBalance(eth.accounts[1])
0
没有挖矿,也没有转账,当前余额均为0。
> miner.start()
执行效果:
> miner.start()
INFO [07-16|07:56:48] Updated mining threads threads=0
INFO [07-16|07:56:48] Transaction pool price threshold updated price=18000000000
INFO [07-16|07:56:48] Starting mining operation
null
> INFO [07-16|07:56:48] Commit new mining work number=1 txs=0 uncles=0 elapsed=166.057µs
INFO [07-16|07:56:50] Successfully sealed new block number=1 hash=2a840c…6e5118
INFO [07-16|07:56:50] mined potential block number=1 hash=2a840c…6e5118
INFO [07-16|07:56:50] Commit new mining work number=2 txs=0 uncles=0 elapsed=165.72µs
INFO [07-16|07:56:50] Successfully sealed new block number=2 hash=f220e8…ffef15
INFO [07-16|07:56:50] mined potential block number=2 hash=f220e8…ffef15
INFO [07-16|07:56:50] Commit new mining work number=3 txs=0 uncles=0 elapsed=133.196µs
执行后开始刷屏,停止挖矿
> miner.stop()
再次查看余额
> eth.getBalance(eth.coinbase)
65000000000000000000
>
> eth.getBalance(eth.accounts[0])
0
>
如果挖矿不成功,请更换目录,重新创建区块链
单位是wei,矿工得到奖励,其他账户为零。
可以使用web3接口,将金额进行转换成ether单位,每个块奖励5个ether,共13个块,所以是5 * 13 = 65个奖励
> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
65
> eth.blockNumber
13
>
> admin.peers
[]
>
当前网络仅一个节点,所以返回空
步骤同上
geth --datadir "./node2" init genesis.json
geth --datadir "./node2" --networkid 72 --port 30302
注意:
==init操作千万不要忘记执行,直接执行启动也会成功运行,但是后续添加节点等总是失败,一定要注意!==
==此处我们创建node2,port修改为30302,但是没有指定 console,这是为了演示attach命令==
==两个节点想互连的话,必须都指定--networkid 72==
geth attach ipc:note2/geth.ipc
执行效果:
[duke ~/ethStudy/myPrivateNet]$ geth attach ipc:note2/geth.ipc
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
此处geth.ipc在node2下面,这个节点启动后,自动生成,节点停止后自动删除。
在node2中,创建新账户
> personal.newAccount("111")
"0x6cf4d0785589497d3b5fca83571097f5cd6df352"
>
> personal.newAccount("111")
"0x1757a2db3fd67f0724bffffc33428faa7e9552f7"
>
> admin.nodeInfo
执行结果:
> admin.nodeInfo
{
enode: "enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302",
id: "3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197",
ip: "192.168.1.65",
listenAddr: "[::]:30302",
name: "Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3",
ports: {
discovery: 30302,
listener: 30302
},
protocols: {
eth: {
config: {
chainId: 72,
eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000",
eip155Block: 0,
eip158Block: 0,
homesteadBlock: 0
},
difficulty: 1725312,
genesis: "0x942f596f99dc8879b426b59080824662e1f97587353d087487fea0a0e2a2588a",
head: "0x17fa69432921727241ea299ab7f12611e09bb5a8473e71f1250f89832661ed86",
network: 72
}
}
}
>
encode字段能唯一标识这个节点,我们将它添加到node1中。
也可以直接获取enode字段
> admin.nodeInfo.enode
"enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302"
>
切换到node1终端,执行下面的命令:
> admin.addPeer("enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302")
添加后,查看节点信息,可以看到,已经添加进来
> admin.peers
[{
caps: ["eth/63"],
id: "3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197",
name: "Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3",
network: {
inbound: false,
localAddress: "192.168.0.107:57154",
remoteAddress: "192.168.1.65:30302",
static: true,
trusted: false
},
protocols: {
eth: {
difficulty: 16384,
head: "0x942f596f99dc8879b426b59080824662e1f97587353d087487fea0a0e2a2588a",
version: 63
}
}
}]
>
由node1账户向node2账户发送一笔交易
在node2中获取第一个账户的地址,该账户当前余额为零
> eth.accounts[0]
"0x6cf4d0785589497d3b5fca83571097f5cd6df352"
> eth.getBalance(eth.accounts[0])
0
切换回node1,执行如下命令:
查看node1中账户余额状态:
> eth.getBalance(eth.accounts[0])
0
> eth.getBalance(eth.accounts[1])
65000000000000000000
我们使用accounts[1]向node2的accounts[0]转账15个ether
> eth.sendTransaction({from : eth.accounts[1], to: "0x6cf4d0785589497d3b5fca83571097f5cd6df352", value: web3.toWei(15, "ether")})
>
此时会报错如下
Error: authentication needed: password or unlock
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at :1:2
>
这说明当前账户被锁定,需要解锁后才能发起交易。
> personal.unlockAccount(eth.accounts[1])
效果:
> personal.unlockAccount(eth.accounts[1])
Unlock account 0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f
Passphrase:
true
>
重新发送交易,可以看到如下效果,交易创建成功,同时返回交易hash
> eth.sendTransaction({from : eth.accounts[1], to: "0x6cf4d0785589497d3b5fca83571097f5cd6df352", value: web3.toWei(15, "ether")})
INFO [07-16|08:38:58] Submitted transaction fullhash=0x1cd7f14436a088586d9bb75db6cadec08f4293d270b21c72b237605d4b921bea recipient=0x6cf4D0785589497D3B5fca83571097F5cD6dF352
"0x1cd7f14436a088586d9bb75db6cadec08f4293d270b21c72b237605d4b921bea"
>
交易发送到网络中后,需要矿工打包才能写入区块,查看当前该交易的状态
>txpool.status
效果
> txpool.status
{
pending: 1, //<--- 等待被打包
queued: 0
}
>
在node2中启动挖矿,然后停止挖矿,在node1中重新查看
node2:
> miner.start()
null
> miner.stop()
true
>
 node1:
> INFO [07-16|08:41:44] Block synchronisation started
INFO [07-16|08:41:44] Imported new chain segment blocks=2 txs=1 mgas=0.021 elapsed=565.360ms mgasps=0.037 number=15 hash=b2be14…d19460 cache=3.33kB
INFO [07-16|08:41:44] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=4.209ms mgasps=0.000 number=16 hash=752b6a…2164c9 cache=3.73kB
INFO [07-16|08:41:44] Mining too far in the future wait=2s
INFO [07-16|08:41:47] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=4.799ms mgasps=0.000 number=17 hash=68b112…82e15e cache=4.12kB
INFO [07-16|08:41:47] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=4.190ms mgasps=0.000 number=18 hash=9ca0f2…2685c9 cache=4.51kB
INFO [07-16|08:41:47] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=2.166ms mgasps=0.000 number=19 hash=7376ea…8c9461 cache=4.90kB
INFO [07-16|08:41:47] Mining too far in the future wait=2s
INFO [07-16|08:41:51] Imported new chain segment blocks=1 txs=0 mgas=0.000 elapsed=4.604ms mgasps=0.000 number=20 hash=35227e…9c2aa3 cache=5.29kB
>
> txpool.status
{
pending: 0,
queued: 0
}
>
可以看到,pending数目变为0,说明交易已经被打包到区块内。
查看交易后的金额,
node1中:
> eth.getBalance(eth.accounts[1])
49999622000000000000
>
node1 的 accounts[1] 账户变为 65 - 15 - 手续费 = 49.99,正确
node2中:
> eth.getBalance(eth.accounts[0])
50000378000000000000
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
50.000378
>
我们发现,accounts[0]的值并非15,为何?
原因是这个账户也是coinbase账户,刚刚参与了挖矿,得到了挖矿的奖励
> eth.blockNumber
20
当前区块数为20,有13个块是从node1节点同步过来的,刚刚我们运行miner.start()一共产生了7个区块,
每个区块奖励5, 共得到奖励 35ether,加上转账获得的15个,为50,再加上手续费,所以总数为50.000378。
Ethereum默认安装目录
我们启动Ethereum客户端,此时连接的是测试网络:
我们通过命令行,查看它起的节点命令如下:
Ethereum启动geth命令:
Mac查看方式 :
ps -ef | grep geth
返回如下信息:
/Users/duke/Library/Application Support/Mist/binaries/Geth/unpacked/geth --testnet --syncmode light --cache 1024 --ipcpath /Users/duke/Library/Ethereum/geth.ipc
Windows查看方式:
wmic process where caption="geth.exe" get caption,commandline /value
请自行查看。
Ethereum客户端每次都要指定特定的geth.ipc,指定--testnet 来和测试网络连接,--testnet参数就是Ropsten网络。
我们可以事先拉起一个geth服务,其中将存储数据参数指定为我们的私有网络,这样Mist在启动时,就会自动连接到我们的服务,而不会重新起服务。
Mac:
geth --datadir ./node1 --ipcpath /Users/duke/Library/Ethereum/geth.ipc
Windows的ipc是固定的:
geth --datadir .\node1 --ipcpath \\.\pipe\geth.ipc
重新打开Etherueum钱包,界面如下:
在命令行,我们通过attach建立一个终端,查看账户信息,我们发现与图形界面一致。
Mac:
geth attach ipc:/Users/duke/Library/Ethereum/geth.ipc
Windows :
geth attach ipc:\\.\pipe\geth.ipc
==注意,此时的ipc应该使用上面的geth.ipc,注意路径,这个钱包自动生成的,并不在我们的node1目录下面。==
执行
> eth.accounts
["0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8", "0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f", "0xf3b1e1712a311443db772c55e12c9634912199ab", "0xef89f85b7e276dcf738276df88888298cd9272ee"]
>
至此,我们完成了私有链的搭建
我们学会如下知识点: