以太坊私链搭建



  • 以太坊私链搭建

    系统环境

    Ubuntu16.04

    安装以太坊客户端

    PPA 直接安装

    安装依赖:

    apt install software-properties-common
    

    添加以太坊源:

    add-apt-repository -y ppa:ethereum/ethereum
    apt update
    

    安装 go-ethereum:

    apt install ethereum
    

    geth version查看是否安装成功。

    源码安装

    配置 Go 语言环境

    wget https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz
    tar -C /usr/local -xzf go1.9.linux-amd64.tar.gz
    

    编辑 /etc/profile$HOME/.profile,追加环境变量:

    export PATH=$PATH:/usr/local/go/bin
    

    编辑 $HOME/profile 或 $HOME/.bashrc,追加go 工作目录环境变量:

    export GOPATH=$HOME/gopath
    

    下载和编译 Geth

    安装 C 编译器:

    apt install -y build-essential
    

    下载最新源码:

    git clone https://github.com/ethereum/go-ethereum
    

    编译安装:

    cd go-ethereum
    make geth
    

    geth version 查看是否安装成功。按照上述,追加变量

    Solidity 编译器安装

    PPA 直接安装

    add-apt-repository ppa:ethereum/ethereum
    apt update
    apt install solc
    

    官方推荐浏览器的IDE:Remixhttps://remix.ethereum.org```

    私有链搭建

    配置初始状态

    定义区块,区块信息写在JSON的配置文件里。比如;

    {
      "config": {
        "chainID": 1024,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
      },
      "alloc": {},
      "coinbase": "0x0000000000000000000000000000000000000000",
      "difficulty": "0x400",
      "extraData": "0x0",
      "gasLimit": "0x2fefd8",
      "nonce": "0xdeadbeefdeadbeef",
      "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x00"
    }
    

    Geth 1.6+以太坊提供一个区块的向导工具:puppeth。并提供更适合在私有链使用的CliquePoA共识算法

    初始化:写入区块

    初始化区块,将上面的区块信息写入到链中。新建目录存放区块链数据,
    目录结构应该为:

    privatechain
    ├── data0
    └── genesis.json
    

    cd到privatechain 中,执行初始化命令:

    cd privatechain
    geth --datadir data0 init genesis.json
    

    geth init,表示初始化区块链,命令可以带有选项和参数

    以上操作将会读取 genesis.json 文件,根据其内容,将区块写到区块链中。如果输出一下内容,说明初始化成功

    WARN [01-29|11:01:09] No etherbase set and no accounts found as default
    INFO [01-29|11:01:09] Allocated cache and file handles         database=/root/work/privatechain/data0/geth/chaindata cache=16 handles=16
    INFO [01-29|11:01:09] Writing custom genesis block
    INFO [01-29|11:01:09] Successfully wrote genesis state         database=chaindata                                    hash=84e71d…97246e
    INFO [01-29|11:01:09] Allocated cache and file handles         database=/root/work/privatechain/data0/geth/lightchaindata cache=16 handles=16
    INFO [01-29|11:01:09] Writing custom genesis block
    INFO [01-29|11:01:09] Successfully wrote genesis state         database=lightchaindata                                    hash=84e71d…97246e
    

    初始化成功后,会在数据目录 data0 中生成 geth 和 keystore 两个文件夹,
    此时目录结构应该为:

    privatechain
    ├── data0
    │   ├── geth
    │   │   ├── chaindata
    │   │   │   ├── 000001.log
    │   │   │   ├── CURRENT
    │   │   │   ├── LOCK
    │   │   │   ├── LOG
    │   │   │   └── MANIFEST-000000
    │   │   └── lightchaindata
    │   │       ├── 000001.log
    │   │       ├── CURRENT
    │   │       ├── LOCK
    │   │       ├── LOG
    │   │       └── MANIFEST-000000
    │   └── keystore
    └── genesis.json
    

    其中 geth/chaindata中存放的是区块数据,keystore 中存放的是账户数据。

    启动私有链节点

    初始化完成后,就有了一条自己的私有链,之后就可以启动自己的私有链节点并做一些操作
    启动节点并进入交互式控制台

    geth --identity "TestNode" --rpc --rpcport "8545" --datadir data0 --port "30303" --nodiscover console
    

    参数说明如下:

    –identity:指定节点 ID;
    –rpc:表示开启 HTTP-RPC 服务;
    –rpcport:指定 HTTP-RPC 服务监听端口号(默认为 8545);
    –datadir:指定区块链数据的存储位置;
    –port:指定和其他节点连接所用的端口号(默认为 30303);
    –nodiscover:关闭节点发现机制,防止加入有同样初始配置的陌生节点。
    

    运行上面的命令后,就启动了区块链节点并进入了该节点的控制台:

    ...
    Welcome to the Geth JavaScript console!
    
    instance: Geth/TestNode/v1.6.7-stable-ab5646c5/linux-amd64/go1.8.1
     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
    

    这个是一个交互式 JavaScript环境,能执行 JavaScript 代码,在这个环境里也内置一些用来操作以太坊的 JavaScript 对象,
    比如:

    eth:包含一些跟操作区块链相关的方法;
    net:包含一些查看p2p网络状态的方法;
    admin:包含一些与管理节点相关的方法;
    miner:包含启动&停止挖矿的一些方法;
    personal:主要包含一些管理账户的方法;
    txpool:包含一些查看交易内存池的方法;
    web3:包含了以上对象,还包含一些单位换算的方法。
    

    控制台操作

    常用命令有:

    personal.newAccount():创建账户;
    personal.unlockAccount():解锁账户;
    eth.accounts:枚举系统中的账户;
    eth.getBalance():查看账户余额,返回值的单位是 Wei(Wei 是以太坊中最小货币面额单位,类似比特币中的聪,1 ether = 10^18 Wei);
    eth.blockNumber:列出区块总数;
    eth.getTransaction():获取交易;
    eth.getBlock():获取区块;
    miner.start():开始挖矿;
    miner.stop():停止挖矿;
    web3.fromWei():Wei 换算成以太币;
    web3.toWei():以太币换算成 Wei;
    txpool.status:交易池中的状态;
    admin.addPeer():连接到其他节点;
    

    这些命令都能 Tab 键自动补全,所以没必要全部记住
    例如:

    一、创建账户
    > personal.newAccount()
    Passphrase:
    Repeat passphrase:
    "0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802"
    > INFO [01-29|11:07:44] New wallet appeared                     url=keystore:///root/work/privatech… status=Locked
    

    输入两遍密码后,会生成账户地址。

    再创建一个账户:

    > personal.newAccount()
    Passphrase:
    Repeat passphrase:
    "0x02bee2a1582bbf58c42bbdfe7b8db4685d4d4c62"
    > INFO [01-29|11:08:15] New wallet appeared                     url=keystore:///root/work/privatech… status=Locked
    

    查看刚刚创建的两个账户:

    > eth.accounts
    ["0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802", "0x02bee2a1582bbf58c42bbdfe7b8db4685d4d4c62"]
    

    查看账户余额

    > eth.getBalance(eth.accounts[0])
    0
    > eth.getBalance(eth.accounts[1])
    0
    

    启动&停止挖矿

    启动挖矿:

    > miner.start(1)
    

    Tips:第一次启动挖矿会先生成挖矿所需的 DAG 文件,这个过程有点慢,等进度达到 100% 后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。
    停止挖矿,在 console 中输入:

    > miner.stop()
    

    挖到一个区块会奖励5个以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做 coinbase,默认情况下 coinbase 是本地账户中的第一个账户,可以通过 miner.setEtherbase() 将其他账户设置成 coinbase

    发送交易

    目前,账户 0 已经挖到了 3 个块的奖励,账户 1 的余额还是0:

    > eth.getBalance(eth.accounts[0])
    15000000000000000000
    > eth.getBalance(eth.accounts[1])
    0
    

    我们要从账户 0 向账户 1 转账,所以要先解锁账户 0,才能发起交易:

    > personal.unlockAccount(eth.accounts[0])
    Unlock account 0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802
    Passphrase: 
    true
    

    发送交易,账户 0 -> 账户 1:

    > amount = web3.toWei(5,'ether')
    "5000000000000000000"
    > eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})
    INFO [01-29|11:09:12] Submitted transaction                    fullhash=0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce recipient=0x02bee2a1582bbf58c42bbdfe7b8db4685d4d4c62
    "0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce"
    

    此时如果没有挖矿,用 txpool.status 命令可以看到本地交易池中有一个待确认的交易,可以使用 eth.getBlock("pending", true).transactions 查看当前待确认交易。

    使用 miner.start() 命令开始挖矿:

    > miner.start(1);admin.sleepBlocks(1);miner.stop();
    

    新区块挖出后,挖矿结束,查看账户 1 的余额,已经收到了账户 0 的以太币:

    > web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')
    5
    

    查看交易和区块

    查看当前区块总数:

    > eth.blockNumber
    4
    

    通过交易 Hash 查看交易(Hash 值包含在上面交易返回值中):

    > eth.getTransaction("0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce")
    {
      blockHash: "0xdc1fb4469bf4613821c303891a71ff0d1f5af9af8c10efdd8bcd8b518533ee7d",
      blockNumber: 4,
      from: "0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802",
      gas: 90000,
      gasPrice: 18000000000,
      hash: "0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce",
      input: "0x",
      nonce: 0,
      r: "0x4214d2d8d92efc3aafb515d2413ecd45ab3695d9bcc30d9c7c06932de829e064",
      s: "0x42822033225a2ef662b9b448576e0271b9958e1f4ec912c259e01c84bd1f6681",
      to: "0x02bee2a1582bbf58c42bbdfe7b8db4685d4d4c62",
      transactionIndex: 0,
      v: "0x824",
      value: 5000000000000000000
    }
    

    通过区块号查看区块:

    > eth.getBlock(4)
    {
      difficulty: 131072,
      extraData: "0xd783010607846765746887676f312e382e31856c696e7578",
      gasLimit: 3153874,
      gasUsed: 21000,
      hash: "0xdc1fb4469bf4613821c303891a71ff0d1f5af9af8c10efdd8bcd8b518533ee7d",
      logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      miner: "0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802",
      mixHash: "0x6df88079cf4fbfae98ad7588926fa30becddf4b8b55f93f0380d82ce0533338c",
      nonce: "0x39455ee908666993",
      number: 4,
      parentHash: "0x14fe27755d6fcc704f6b7018d5dc8193f702d89f2c7807bf6f0e402a2b0a29d9",
      receiptsRoot: "0xfcb5b5cc322998562d96339418d08ad8e7c5dd87935f9a3321e040344e3fd095",
      sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
      size: 651,
      stateRoot: "0xdd4a0ce76c7e0ff149853dce5bb4f99592fb1bc3c5e87eb07518a0235ffacd8c",
      timestamp: 1505202063,
      totalDifficulty: 525312,
      transactions: ["0x9f5e61f3d686f793e2df6378d1633d7a9d1df8ec8c597441e1355112d102a6ce"],
      transactionsRoot: "0xbb909845183e037b15d24fe9ad1805fd00350ae04841aa774be6af96e76fdbf9",
      uncles: []
    }
    

    连接到其他节点

    可以通过 admin.addPeer() 方法连接到其他节点,两个节点要要指定相同的 chainID。
    比如:

    > admin.nodeInfo.enode
    "enode://d465bcbd5c34da7f4b8e00cbf9dd18e7e2c38fbd6642b7435f340c7d5168947ff2b822146e1dc1b07e02f7c15d5ca09249a92f1d0caa34587c9b2743172259ee@[::]:30303"
    
    > admin.addPeer("enode://d465bcbd5c34da7f4b8e00cbf9dd18e7e2c38fbd6642b7435f340c7d5168947ff2b822146e1dc1b07e02f7c15d5ca09249a92f1d0caa34587c9b2743172259ee@127.0.0.1:30304")
    addPeer()
    

    通过 admin.peers 可以查看连接到的其他节点信息,通过 net.peerCount 可以查看已连接到的节点数量

    智能合约操作

    创建和编译智能合约

    新建Solidity智能合约文件,重命名为 testContract.sol将输入的两个数相乘后输出:

    pragma solidity ^0.4.0;
    contract TestContract
    {
        function multiply(uint a, uint b) returns (uint)
        {
            return a * b;
        }
    }
    

    编译智能合约,编译后的 EVM 二进制码:

    $ solc --bin testContract.sol
    
    ======= testContract.sol:TestContract =======
    Binary:
    6060604052341561000f57600080fd5b5b60b48061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063165c4a1614603d575b600080fd5b3415604757600080fd5b60646004808035906020019091908035906020019091905050607a565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820b494a4b3879b3810accf64d4cc3e1be55f2f4a86f49590b8a9b8d7009090a5d30029
    
    $ solc --abi testContract.sol
    
    ======= testContract.sol:TestContract =======
    Contract JSON ABI
    [{"constant":false,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"multiply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]
    
    > code = "0x6060604052341561000f57600080fd5b5b60b48061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063165c4a1614603d575b600080fd5b3415604757600080fd5b60646004808035906020019091908035906020019091905050607a565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820b494a4b3879b3810accf64d4cc3e1be55f2f4a86f49590b8a9b8d7009090a5d30029"
    > abi = [{"constant":false,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"multiply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]
    

    部署智能合约

    使用账户 0 来部署合约,
    解锁账户:

    > personal.unlockAccount(eth.accounts[0])
    Unlock account 0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802
    Passphrase:
    true
    

    发送部署合约交易:

    > myContract = eth.contract(abi)
    ...
    > contract = myContract.new({from:eth.accounts[0],data:code,gas:1000000})
    
    INFO [01-29|11:11:19] Submitted contract creation              fullhash=0x0a7dfa9cac7ef836a72ed1d5bbfa65c0220347cde4efb067a0b03b15fb70bce1 contract=0x7cbe4019e993f9922b8233502d94890099ee59e6
    {
      abi: [{
          constant: false,
          inputs: [{...}, {...}],
          name: "multiply",
          outputs: [{...}],
          payable: false,
          stateMutability: "nonpayable",
          type: "function"
      }],
      address: undefined,
      transactionHash: "0x0a7dfa9cac7ef836a72ed1d5bbfa65c0220347cde4efb067a0b03b15fb70bce1"
    }
    

    此时如果没有挖矿,txpool.status 可以看到本地交易池有一个待确交易。
    查看当前待确认的交易:

    [{
        blockHash: "0xfedd6fef9f25e96a5a20b5ffcd152e9fe05d193ae0989c25d6197d2441c2c09b",
        blockNumber: 5,
        from: "0x3443ffb2a5ce3f4b80080791e0fde16a3fac2802",
        gas: 1000000,
        gasPrice: 18000000000,
        hash: "0x0a7dfa9cac7ef836a72ed1d5bbfa65c0220347cde4efb067a0b03b15fb70bce1",
        input: "0x6060604052341561000f57600080fd5b5b60b48061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063165c4a1614603d575b600080fd5b3415604757600080fd5b60646004808035906020019091908035906020019091905050607a565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820b494a4b3879b3810accf64d4cc3e1be55f2f4a86f49590b8a9b8d7009090a5d30029",
        nonce: 3,
        r: "0xfe7139a31694a36f946e7182c35c52a21bf31d33994490815f63f6674e84dc93",
        s: "0x4701a984ee93d2a323fac55547b31534b11915599de75202a1d62061241fefbf",
        to: null,
        transactionIndex: 0,
        v: "0x823",
        value: 0
    }]
    

    miner.start() 命令开始挖矿,一段时间后交易会被确认,随新区块进入区块链。

    调用智能合约

    > contract.multiply(2, 4, {from:eth.accounts[0]})
    
    INFO [01-29|11:15:14] Submitted transaction                    fullhash=0x29b47d580ba6ccb2445aa3ebdcb14567bd5cbc6004edef7a4064c36e0606bca2 recipient=0x7cbe4019e993f9922b8233502d94890099ee59e6
    "0x29b47d580ba6ccb2445aa3ebdcb14567bd5cbc6004edef7a4064c36e0606bca2"
    

    本地查看返回结果:

    > contract.multiply(2,4)
    8
    

Log in to reply