Geth是用Go语言编写的与以太坊网络交互的客户端。
安装参考:https://geth.ethereum.org/downloads/
Geth命令参考文章:
以下部署的前提条件是已安装Geth客户端。
1、初始化创世区块
搭建以太坊网络需要从一个创世区块开始,创建一个名为genesis.json的文件,文件内容如下:
{
"config": {
"chainId": 666,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x1",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"alloc" : {}
}
参数解析:
参数名称 | 解释 |
---|---|
config | 对私有链的配置信息,详见:https://github.com/ethereum/go-ethereum/blob/feeccdf4ec1084b38dac112ff4f86809efd7c0e5/params/config.go#L71 |
config.chainId | 链的id,不同id的链不能互相通信,在 geth 命令中的 --networkid 参数需要与 chainId 的值一致 |
config.homesteadBlock | Homestead 硬分叉区块高度,不需要关注填写0即可 |
config.eip155Block | EIP 155硬分叉高度,不需要关注 |
config.eip158Block | EIP 158硬分叉高度,不需要关注 |
coinbase | 矿工账号 |
difficulty | 设置挖矿难度,这里设置较小难度 |
extraData | 附件信息,可以填写个性信息 |
gasLimit | 对gas消耗总量限制,用来限制区块能包含的交易信息的总和 |
nonce | 一个64位的随机数,用于挖矿 |
mixhash | 与nonce配合挖矿,由上一个区块的一部分生成的hash。注意他和nonce的设置需要满足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章节所描述的条件。 |
parentHash | 上一个区块的hash,因为是创世区块,所以为0 |
timestamp | 创世区块的时间戳 |
alloc | 用来预置账号以及账号的以太币数量,因为是私有链挖矿比较容易,可以不用预设值有币的账户,需要时自己创建即可 |
2、初始化创世区块
用带init命令
geth --datadir /Users/****/ehtdata/ init
参数解释:
--datadir 指定区块数据存放目录
运行成功后如下图所示:
3、启动创世区块
使用Geth的console命令,进入命令行交互模式,并且将日志输出到eth.log文件中,退出交互模式使用exit命令。
geth --datadir /Users/****/ehtdata/ \
--networkid 666 \
--identity "bootnode" \
--port 30303 \
--rpc \
--rpcport 8545 \
--rpccorsdomain "*" \
--nodiscover \
--verbosity 4 \
console 2>> eth.log
参数说明:
参数名称 | 解释 |
---|---|
--networkid | 区块网络id,和genesis.json中的chainId一致 |
--identity | 节点id |
--port | geth节点端口号,默认就是30303 |
--rpc | 启用rpc服务,默认端口号8545 |
--rpcport | rpc服务端口号 |
--rpccorsdomain | rpc域名,这里配置的是通配符*,允许任意域名访问,也可以指定具体的域名,如“http://sixdays.top” |
--nodiscover | 关闭节点自动发现 |
--verbosity | 日志级别 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3) |
启动成功如下图所示:
[图片上传中...(image.png-854f76-1557751444373-0)]
我们可以重新打开一个shell,使用命令
tail -f eth.log
实时查看区块的日志,如下图所示:
创建成功后,可以使用命令:
eth.getBlock(0)
查看我们刚才配置的创世区块内容,如下图所示:
至此,我们的私有链已经创建成功了,接下来,我们就在这个私有链上做一些挖矿、部署合约等操作。
Geth的console实际上是一个JavaScript的运行环境,我们可以在这个环境下运行以太坊的RPC和web3的方法。
JSON-RPC文档:https://github.com/ethereum/wiki/wiki/JSON-RPC
JavaScript-API文档:https://github.com/ethereum/wiki/wiki/JavaScript-API
除此之外,Geth提供了一些内置方法:https://github.com/ethereum/go-ethereum/wiki/Management-APIs
4、创建账号
使用Geth的内置命令,输入账号密码,创建一个新账号
>personal.newAccount() //创建账号
Passphrase:
Repeat passphrase:
"0xfa8d4ded7fe1fec96c1b10443bea261195f233bb"
> personal.listAccounts //查看已有账号,也可使用eth.accounts
["0xfa8d4ded7fe1fec96c1b10443bea261195f233bb"]
> eth.getBalance("0xfa8d4ded7fe1fec96c1b10443bea261195f233bb") //查看账号里的余额
0
5、挖矿,赚取eth
> miner.start() //开启挖矿
null
> eth.getBalance("0xfa8d4ded7fe1fec96c1b10443bea261195f233bb") //过一段时间后查看余额
65000000000000000000
> eth.blockNumber //查看区块高度
118
转账测试
> personal.newAccount("linjing1") //创建一个新账号
"0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53"
> eth.accounts
["0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53"]
> personal.unlockAccount("0xfa8d4ded7fe1fec96c1b10443bea261195f233bb","linjing1") //解锁账号
true
> eth.getBalance(eth.accounts[1]) //账号1(0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53)的余额目前为0
0
> eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53" gas: "0x76c0", value: "0x9184e72a"}) //转账
"0xed1a157b759e00d9a767b0849f81f5e6a6cf0187dcfb43a5307e51eb436b3e92" //返回的是交易hash
> eth.getTransaction("0xed1a157b759e00d9a767b0849f81f5e6a6cf0187dcfb43a5307e51eb436b3e92") //查看交易信息
{
blockHash: "0x93d566d17a4b9d252a18cfd2a4c10fdca164c87f883799052ed229d9c6397c7a",
blockNumber: 661,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: 30400,
gasPrice: 1000000000,
hash: "0xed1a157b759e00d9a767b0849f81f5e6a6cf0187dcfb43a5307e51eb436b3e92",
input: "0x",
nonce: 0,
r: "0xade3453541928fe6e67b5137f98f81ddb975100a79e0639b954b66420bd711e5",
s: "0x4f6e9b2aa27114bbe6f4e609426d188315d18a090bdfe3901694eda3664c201d",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: 0,
v: "0x557",
value: 2441406250
}
> web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") //将单位转化为以太坊
2.44140625e-9
6、创建新节点并加入网络
区块链是一种p2p的网络模型,到目前为止,我们搭建的是一个单机版的私有链。现在我们新建一个节点,并接入到我们的网络中。新节点创建需要注意:
- 新节点的networkid要保持一致,这里我用的是666
- 新节点使用同一个创世区块
- 如果是在同一个机器上创建多个节点,注意端口号区分
$geth --datadir /Users/linjingjing/Documents/project/node2 init genesis.json
INFO [05-13|17:53:53.368] Maximum peer count ETH=25 LES=0 total=25
INFO [05-13|17:53:53.387] Allocated cache and file handles database=/Users/linjingjing/Documents/project/node2/geth/chaindata cache=16 handles=16
INFO [05-13|17:53:53.389] Writing custom genesis block
INFO [05-13|17:53:53.390] Persisted trie from memory database nodes=0 size=0.00B time=12.925µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [05-13|17:53:53.390] Successfully wrote genesis state database=chaindata hash=c626d8…b99ce5
INFO [05-13|17:53:53.391] Allocated cache and file handles database=/Users/linjingjing/Documents/project/node2/geth/lightchaindata cache=16 handles=16
INFO [05-13|17:53:53.394] Writing custom genesis block
INFO [05-13|17:53:53.394] Persisted trie from memory database nodes=0 size=0.00B time=2.492µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [05-13|17:53:53.394] Successfully wrote genesis state database=lightchaindata
$geth --datadir /Users/linjingjing/Documents/project/node2 \
--port 30304 \
--networkid 666 \
--identity "node2" \
--rpc \
--rpcport 9545 \
--rpccorsdomain "*" \
--nodiscover \
--verbosity 4 \
console 2>> node2.log
接下来,让两个节点建立联系,有三种方法
方法一,使用admin.addPeer()
1、在第一个节点中使用admin.nodeInfo.enode命令查看enode信息,如下所示:
> admin.nodeInfo.enode
"enode://2c4eb7a267b1c00a8550e0237277234ca4bf8076b48268693e689f0657d6eb40eb88ee3f4d619b2361471c72881918a300a0de2943f6a29bd2a7c78e7a820d85@127.0.0.1:30303?discport=0"
2、在新建节点中使用admin.addPeer命令添加联系,如下所示:
admin.addPeer("enode://2c4eb7a267b1c00a8550e0237277234ca4bf8076b48268693e689f0657d6eb40eb88ee3f4d619b2361471c72881918a300a0de2943f6a29bd2a7c78e7a820d85@127.0.0.1:30303?discport=0")
true
3、在两个节点中分别使用admin.peers验证
节点一
> admin.peers
[{
caps: ["eth/63"],
enode: "enode://bc44cdf9ebc14a15a052d6c62717a2503f493f673303d193da7e2951d466b9758dbac840bbc08f823a765bb5ea8324e5c3706d3a6473b3c392eda56e55ab7555@127.0.0.1:61413",
id: "c833b932f0c7329be2d3b15fffced1b0fa7c5f2913d1b2879285021a0f176e3d",
name: "Geth/node2/v1.8.27-stable-4bcc0a37/darwin-amd64/go1.11.9",
network: {
inbound: true,
localAddress: "127.0.0.1:30303",
remoteAddress: "127.0.0.1:61413",
static: false,
trusted: false
},
protocols: {
eth: {
difficulty: 1,
head: "0xc626d8be5d39286154d44074a2f0a31192792385e89d6f1f60dea77be6b99ce5",
version: 63
}
}
}]
节点二
> admin.peers
[{
caps: ["eth/63"],
enode: "enode://2c4eb7a267b1c00a8550e0237277234ca4bf8076b48268693e689f0657d6eb40eb88ee3f4d619b2361471c72881918a300a0de2943f6a29bd2a7c78e7a820d85@127.0.0.1:30303?discport=0",
id: "4e299133607ac04682880cd06663a82d72c34c1271df06878df739597b759658",
name: "Geth/bootnode/v1.8.27-stable-4bcc0a37/darwin-amd64/go1.11.9",
network: {
inbound: false,
localAddress: "127.0.0.1:61413",
remoteAddress: "127.0.0.1:30303",
static: true,
trusted: false
},
protocols: {
eth: {
difficulty: 155767989,
head: "0xc758c0e5e2e07c43d71b6513412a6e1bfcb8e3faae3ecdd08e7b53e3ac3a0884",
version: 63
}
}
}]
方法二,使用 static-nodes.json 文件
在--datadir目录中添加static-nodes.json文件,让节点取得联系,文件内容如下所示:
[
"enode://2c4eb7a267b1c00a8550e0237277234ca4bf8076b48268693e689f0657d6eb40eb88ee3f4d619b2361471c72881918a300a0de2943f6a29bd2a7c78e7a820d85@127.0.0.1:30303?discport=0"
]
方法三,使用--bootnodes参数指定enode
$geth --datadir /Users/linjingjing/Documents/project/node2 \
--port 30304 \
--networkid 666 \
--identity "node2" \
--rpc \
--rpcport 9545 \
--rpccorsdomain "*" \
--nodiscover \
--verbosity 4 \
--bootnodes ["enode://2c4eb7a267b1c00a8550e0237277234ca4bf8076b48268693e689f0657d6eb40eb88ee3f4d619b2361471c72881918a300a0de2943f6a29bd2a7c78e7a820d85@127.0.0.1:30303?discport=0"] \
console 2>> node2.log
7、部署智能合约
写了一个简单的合约
pragma solidity >=0.5.1;
contract Test {
uint256 public A = 0;
function set (uint256 a) public {
A = a;
}
function get() view public returns(uint256) {
return A;
}
function sum(uint256 b) view public returns(uint256) {
return A + b;
}
}
将合约使用remix(http://remix.ethereum.org/)进行编译,然后拷贝ABI和Bytecode
ABI
[
{
"constant": true,
"inputs": [
{
"name": "b",
"type": "uint256"
}
],
"name": "sum",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "a",
"type": "uint256"
}
],
"name": "set",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "get",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "A",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
在bejson中转义成字符串
http://www.bejson.com/jsonviewernew/
[{\"constant\":true,\"inputs\":[{\"name\":\"b\",\"type\":\"uint256\"}],\"name\":\"sum\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"A\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]
字节码Bytecode
60806040526000805534801561001457600080fd5b50610194806100246000396000f3fe60806040526004361061005c576000357c010000000000000000000000000000000000000000000000000000000090048063188b85b41461006157806360fe47b1146100b05780636d4ce63c146100eb578063f446c1d014610116575b600080fd5b34801561006d57600080fd5b5061009a6004803603602081101561008457600080fd5b8101908080359060200190929190505050610141565b6040518082815260200191505060405180910390f35b3480156100bc57600080fd5b506100e9600480360360208110156100d357600080fd5b810190808035906020019092919050505061014f565b005b3480156100f757600080fd5b50610100610159565b6040518082815260200191505060405180910390f35b34801561012257600080fd5b5061012b610162565b6040518082815260200191505060405180910390f35b600081600054019050919050565b8060008190555050565b60008054905090565b6000548156fea165627a7a723058203033498bf3661ba9be9645b58fc484ca960635e7cabc7879bda415d4e13094170029
准备好了合约,接下来就是部署了
1、定义bytecode和abi
> var bytecode = "0x60806040526000805534801561001457600080fd5b50610194806100246000396000f3fe60806040526004361061005c576000357c010000000000000000000000000000000000000000000000000000000090048063188b85b41461006157806360fe47b1146100b05780636d4ce63c146100eb578063f446c1d014610116575b600080fd5b34801561006d57600080fd5b5061009a6004803603602081101561008457600080fd5b8101908080359060200190929190505050610141565b6040518082815260200191505060405180910390f35b3480156100bc57600080fd5b506100e9600480360360208110156100d357600080fd5b810190808035906020019092919050505061014f565b005b3480156100f757600080fd5b50610100610159565b6040518082815260200191505060405180910390f35b34801561012257600080fd5b5061012b610162565b6040518082815260200191505060405180910390f35b600081600054019050919050565b8060008190555050565b60008054905090565b6000548156fea165627a7a723058203033498bf3661ba9be9645b58fc484ca960635e7cabc7879bda415d4e13094170029"
> var abi = JSON.parse('[{\"constant\":true,\"inputs\":[{\"name\":\"b\",\"type\":\"uint256\"}],\"name\":\"sum\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"A\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]')
undefined
2、创建合约对象
> test = web3.eth.contract(abi)
{
abi: [{
constant: true,
inputs: [{...}],
name: "sum",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}, {
constant: true,
inputs: [],
name: "A",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}],
eth: {
accounts: ["0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9"],
blockNumber: 1049,
coinbase: "0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9",
compile: {
lll: function(),
serpent: function(),
solidity: function()
},
defaultAccount: undefined,
defaultBlock: "latest",
gasPrice: 1000000000,
hashrate: 0,
mining: false,
pendingTransactions: [],
protocolVersion: "0x3f",
syncing: false,
call: function(),
chainId: function(),
contract: function(abi),
estimateGas: function(),
filter: function(options, callback, filterCreationErrorCallback),
getAccounts: function(callback),
getBalance: function(),
getBlock: function(),
getBlockNumber: function(callback),
getBlockTransactionCount: function(),
getBlockUncleCount: function(),
getCode: function(),
getCoinbase: function(callback),
getCompilers: function(),
getGasPrice: function(callback),
getHashrate: function(callback),
getMining: function(callback),
getPendingTransactions: function(callback),
getProof: function(),
getProtocolVersion: function(callback),
getRawTransaction: function(),
getRawTransactionFromBlock: function(),
getStorageAt: function(),
getSyncing: function(callback),
getTransaction: function(),
getTransactionCount: function(),
getTransactionFromBlock: function(),
getTransactionReceipt: function(),
getUncle: function(),
getWork: function(),
iban: function(iban),
icapNamereg: function(),
isSyncing: function(callback),
namereg: function(),
resend: function(),
sendIBANTransaction: function(),
sendRawTransaction: function(),
sendTransaction: function(),
sign: function(),
signTransaction: function(),
submitTransaction: function(),
submitWork: function()
},
at: function(address, callback),
getData: function(),
new: function()
}
3、预估gas费用
> web3.eth.estimateGas({data: bytecode}) //预估gas
164951
4、解锁账号
> eth.accounts
["0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9"]
> personal.unlockAccount("0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9") //解锁账号
Unlock account 0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9
Passphrase: //输入密码
true
5、部署合约
> contractInstance = test.new({data: bytecode, gas: 1000000, from: "0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9"}, function(e, contract){
if(!e){
if(!contract.address){
console.log("Contract transaction send: Transaction Hash: "+contract.transactionHash+" waiting to be mined...");
}else{
console.log("Contract mined! Address: "+contract.address);
console.log(contract);
}
}else{
console.log(e)
}
})
Contract transaction send: Transaction Hash: 0x2e5850086b9ebc12336ade65943c6685613db6c885fa4a06ff00f168ad8a2206 waiting to be mined...
{
abi: [{
constant: true,
inputs: [{...}],
name: "sum",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}, {
constant: true,
inputs: [],
name: "A",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}],
address: undefined,
transactionHash: "0x2e5850086b9ebc12336ade65943c6685613db6c885fa4a06ff00f168ad8a2206"
}
6、开启挖矿并验证部署
> miner.start() //开启挖矿
null
> Contract mined! Address: 0x3d21428a20aa5c51e7ab1dfc52c89c2c337924e6
[object Object]
> eth.getCode('0x3d21428a20aa5c51e7ab1dfc52c89c2c337924e6') //查看是否部署成功
"0x60806040526004361061005c576000357c010000000000000000000000000000000000000000000000000000000090048063188b85b41461006157806360fe47b1146100b05780636d4ce63c146100eb578063f446c1d014610116575b600080fd5b34801561006d57600080fd5b5061009a6004803603602081101561008457600080fd5b8101908080359060200190929190505050610141565b6040518082815260200191505060405180910390f35b3480156100bc57600080fd5b506100e9600480360360208110156100d357600080fd5b810190808035906020019092919050505061014f565b005b3480156100f757600080fd5b50610100610159565b6040518082815260200191505060405180910390f35b34801561012257600080fd5b5061012b610162565b6040518082815260200191505060405180910390f35b600081600054019050919050565b8060008190555050565b60008054905090565b6000548156fea165627a7a723058203033498bf3661ba9be9645b58fc484ca960635e7cabc7879bda415d4e13094170029"
7、调用合约方法
> contractInstance.get()
0
> contractInstance.set.sendTransaction(9, {from:"0xd6b8bbf4232cfb5d70e6c5a1a37d3250a2a190a9", gas:1000000})
"0x6a2b0d2a5d4175519610e681853e823aa401a0295c10b4be7fb7e2358f8f59d0"
> contractInstance.get()
9
> contractInstance.sum(666)
675
当然,除了使用Geth客户端部署和执行合约外,也可以自己在remix上直接进行部署。
在remix上Environment选择“web3 Provider”,然后填写本地的开启的RPC服务。如下图所示:
此时remix已与我们本地搭建的私有链联通,直接在remix上开发调试以及部署即可。