zoukankan      html  css  js  c++  java
  • 美图DPOS以太坊教程(Docker版)

    一、前言

    最近,需要接触区块链项目的主链开发,在EOSBTCethereum超级账本这几种区块链技术当中,相互对比后,最终还是以go-ethereum为解决方案。
    ethereum为基准去找解决方案,最终找到了2个符合自己要求的方案,分别如下:美图gttc。本来是想用gttc的这个解决方案的,但是它是基于go-ethereum最新源码来进行二次开发的,相对不稳定,因此还是用美图的解决方案了,毕竟这公司大一点,没那么多坑。

    二、源码

    为了方便测试,我们需要将节点最大验证器数修改一下,这样便于我们进行简单测试
    修改consensus/dpos/dpos.go文件的maxValidator

    // 新值
    maxValidatorSize = 21
    // 旧值
    maxValidatorSize = 3
    

    三、构建

    # 进入源码根目录
    docker build . -t meitugeth
    

    注意:构建过程中会用到buildenv.sh文件,会提示无权限,因此你需要给该文件赋予执行权限。

    sudo chmod 777 buildenv.sh
    

    四、部署

    1. 创建节点数据目录

    最好事先创建好相应的目录,否则运行后,动态创建目录,会有权限的问题,当然也可以通过命令进行设置目录的权限。

    mkdir ~/data
    mkdir ~/data/meitu
    mkdir ~/data/meitu/node1
    mkdir ~/data/meitu/node2
    mkdir ~/data/meitu/node3
    

    2. 编写docker-compose.yml文件

    version: '3'
    services: 
      meitu_node_1:
        image: meitugeth
        container_name: meitu_node_1
        build: 
          context: .
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303
        ports: 
          - 15450:8545
          - 15460:8546
          - 10303:30303
          - 10303:30303/udp
          - 10304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node1/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    
      meitu_node_2:
        image: meitugeth
        container_name: meitu_node_2
        build: 
          context: .
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303
        depends_on: 
          - meitu_node_1
        ports: 
          - 25450:8545
          - 25460:8546
          - 20303:30303
          - 20303:30303/udp
          - 20304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node2/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    
      meitu_node_3:
        image: meitugeth
        container_name: meitu_node_3
        build: 
          context: .
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303
        depends_on: 
          - meitu_node_1
        ports: 
          - 45450:8545
          - 45460:8546
          - 40303:30303
          - 40303:30303/udp
          - 40304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node3/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    

    3. 启动节点

    在根目录下启动3个以太坊节点

    docker-compose up --build -d
    

    4. 启动思路

    美图以太坊这块有2种启动网络:

    • 在创世块里配置好第一验证节点,然后启动
    • 混合POW和DPOS,用POW进行投票,产生第一批验证节点,并自动切换到DPOS

    5. 首次启动

    1. 进入容器

    进入容器命令如下:

    # 模板
    docker exec -it [容器名|容器ID] /bin/sh
    # 例子
    docker exec -it meitu_node_1 /bin/sh
    docker exec -it meitu_node_2 /bin/sh
    docker exec -it meitu_node_3 /bin/sh
    

    2. 进入geth JavaScript控制台

    # 方式一
    geth attach ipc:/root/.ethereum/geth.ipc
    # 方式二
    docker exec -it meitu_node_1 geth attach ipc:/root/.ethereum/geth.ipc
    # 方式三:使用别名
    alias geth="docker exec -it meitu_node_1 geth attach ipc:/root/.ethereum/geth.ipc"
    

    3. 创建账户

    进入geth JavaScript 控制台后,需哟啊创建账户,命令如下;

    # 模板
    personal.newAccount('名称')
    # 例子
    personal.newAccount('test001')
    "0x849f9442198282fb21539351edb0378463e4c251"
    personal.newAccount('test002')
    "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed"
    personal.newAccount('test003')
    "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02"
    

    创建成功后,需要将返回的地址记录起来

    loop:重复1-3步骤,在node1、node2、node3分别创建账户

    4. 编写创世块配置文件

    将上一步操作生成的地址写入到创世块文件中,三个节点的地址分别为:

    0x849f9442198282fb21539351edb0378463e4c251
    0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed
    0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02
    

    将3个节点地址列入第一批验证人列表

    {
        "config": {
            "chainId": 7777,
            "eip155Block": 0,
            "eip158Block": 0,
            "byzantiumBlock":0,
            "dpos":{
                "validators":[
                    "0x849f9442198282fb21539351edb0378463e4c251",
                    "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed",
                    "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02"
                ]
            }
        },
        "nonce": "0x0000000000000042",
        "difficulty": "0x020000",
        "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "coinbase": "0x0000000000000000000000000000000000000000",
        "timestamp": "0x00",
        "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
        "gasLimit": "0x500000",
        "alloc": {}
    }
    

    创世配置参数说明:

    • nonce:64位随机数,用于挖矿
    • timestamp:创世块的时间戳
    • parentHash:上一个区块的hash值,因为是创世块,所以这个值是0
    • mixHash:与nonce配合用于挖矿,由上一个区块的一部分生成hash
    • extraData:附加信息,任意填写
    • gasLimit:对GAS的消耗总量限制,用来限制区块能包含的交易信息总和
    • difficulty:难度值,越大越难
    • coinbase:矿工账号,第一个区块挖出后将给这个矿工账号发送奖励的以太币
    • alloc:预设账号以及账号的以太币数量,测试链挖矿比较容易可以不配置
    • chainId:指定了独立的区块链网络ID,不同ID网络的节点无法互相连接

    5. 初始化创世目录

    1. 删除每个节点下geth目录,保留keystore

    移除geth目录,便于移除旧的无效数据,因为节点刚启动的时候,使用的是默认创世配置,而自定义的又不一样,因此需要移除。

    sudo rm -rf ~/data/meitu/node1/geth
    sudo rm -rf ~/data/meitu/node2/geth
    sudo rm -rf ~/data/meitu/node3/geth
    

    2. 拷贝创世配置到数据目录

    将创世配置拷贝到数据目录中,便于容器内能访问,至于这个目录跟docker-compose.yml映射的目录有关。
    因此,命令也要相应的变更。

    cp genesis.json ~/data/meitu/node1
    cp genesis.json ~/data/meitu/node2
    cp genesis.json ~/data/meitu/node3
    

    3. 初始化创世配置

    进入容器,并执行初始化命令。

    # 节点1
    docker exec -it meitu_node_1 /bin/sh
    geth init /root/.ethereum/genesis.json
    # 节点2
    docker exec -it meitu_node_2 /bin/sh
    geth init /root/.ethereum/genesis.json
    # 节点3
    docker exec -it meitu_node_3 /bin/sh
    geth init /root/.ethereum/genesis.json
    

    **loop:重复在node2、node3上分别执行init

    6. 重启节点网络

    以下命令需要在docker-compose.yml文件的当前目录方可运行。

    # 移除容器
    docker-compose down
    # 启动容器
    docker-compose up -d
    

    7. 查看验证人是否设置成功

    1. 进入容器

    docker exec -it meitu_node_3 /bin/sh
    

    2. 进入geth JavaScript控制台

    geth attach ipc:/root/.ethereum/geth.ipc
    

    3. 执行获取验证人列表命令

    dpos.getValidators()
    ["0x849f9442198282fb21539351edb0378463e4c251", "0x2c08f54d5b324c0175ea53b997f5ce1f61a7e4ed", "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02"]
    

    8. 节点互联

    1. 查看节点是否互联

    admin.peers
    []
    

    返回的数据为[],说明节点之间没有互相发现。

    2. 设置节点互联

    1. 查看每个节点信息
    admin.nodeInfo
    

    确认:enode都不一样,protocols都一样。
    记下三个enode

    "enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[::]:30303"
    "enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413@[::]:30303"
    "enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6@[::]:30303"
    
    2. 查看docker容器网络信息
    # 显示docker所有网络信息
    docker network ls
    # 查看具体网络信息
    docker network inspect [网络名称]
    # 例子
    docker network inspect docker_default
    

    执行查看网络信息命令后,会返回一下内容

    [
        {
            "Name": "docker_default",
            "Id": "984fabf7e51b07c1984114720f98f305cc61cc26546cf9da4bcbbbc36a591351",
            "Created": "2018-12-05T06:06:13.435067024Z",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": null,
                "Config": [
                    {
                        "Subnet": "172.20.0.0/16",
                        "Gateway": "172.20.0.1"
                    }
                ]
            },
            "Internal": false,
            "Attachable": true,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {
                "263f1553108cc8ea00b76598adb6d66649287943c7d0f50d0be02963863ff45c": {
                    "Name": "meitu_node_2",
                    "EndpointID": "9f4114a81133bd490b501ac45fd50fe512309f9096b5468054bd074bad45a07d",
                    "MacAddress": "02:42:ac:14:00:03",
                    "IPv4Address": "172.20.0.3/16",
                    "IPv6Address": ""
                },
                "3acd943c8e0759dc241d7cc623b1a1ca45096480dcd2fd0daa8b951407eb40bf": {
                    "Name": "meitu_node_3",
                    "EndpointID": "0480296c4e8217862358143a084da2d1563cd4f60105e6020434f222320681a5",
                    "MacAddress": "02:42:ac:14:00:04",
                    "IPv4Address": "172.20.0.4/16",
                    "IPv6Address": ""
                },
                "416fe5eb074c24f032d5bd49a6be68cf293a30af17780133b9fa63663e4b7097": {
                    "Name": "meitu_node_1",
                    "EndpointID": "0dc00e9c6f63f844502525b34e87baf62f4de852ac46907951d4b689dd89635f",
                    "MacAddress": "02:42:ac:14:00:02",
                    "IPv4Address": "172.20.0.2/16",
                    "IPv6Address": ""
                }
            },
            "Options": {},
            "Labels": {
                "com.docker.compose.network": "default",
                "com.docker.compose.project": "docker",
                "com.docker.compose.version": "1.23.1"
            }
        }
    ]
    

    找到不同容器中,相应的IP地址。
    记录下每个节点的IP,也可以用127.0.0.1加节点映射到本机的不同网络端口。

    meitu_node_1 172.20.0.2
    meitu_node_2 172.20.0.3
    meitu_node_3 172.20.0.4
    
    3. 添加监视器

    进入节点1 geth JavaScript 控制台后,执行以下命令:

    # 添加节点2的监视器
    admin.addPeer("enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413@[172.20.0.3]:30303")
    # 添加节点3的监视器
    admin.addPeer("enode://e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6@[172.20.0.4]:30303")
    
    4.查看节点网络

    在执行完添加监视器后,执行admin.peers即可看到节点已经互联起来。

    admin.peers
    [{
        caps: ["eth/62", "eth/63"],
        id: "6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413",
        name: "Geth/v1.7.4-stable-a487fc95/linux-amd64/go1.9.7",
        network: {
          localAddress: "172.20.0.2:60018",
          remoteAddress: "172.20.0.3:30303"
        },
        protocols: {
          eth: {
            difficulty: 131189,
            head: "0xa00badd4041033da53c0a34cce5aa59885d7f638e00e1e307b04c02ee640df19",
            version: 63
          }
        }
    }, {
        caps: ["eth/62", "eth/63"],
        id: "e82fecab04e5e902a9e4ea491527ea958d2cdeb83383dfa36562e32a51eedb204a541e00ef0b497704ec0e91017799a73283e53f6dffdeef492a4230626b10b6",
        name: "Geth/v1.7.4-stable-a487fc95/linux-amd64/go1.9.7",
        network: {
          localAddress: "172.20.0.2:36700",
          remoteAddress: "172.20.0.4:30303"
        },
        protocols: {
          eth: {
            difficulty: 131076,
            head: "0xe30ff3a8d1ae16384369c45d106841ef44e83c12eae2e2c66dce1bdccc9ba4d6",
            version: 63
          }
        }
    }]
    

    注意:这一步完成了,仅仅是临时的,每次重启docker之后admin.peers会重新为空。

    9. 配置永久互联

    临时互联不方便,可以将bootnodes配置到启动文件中。
    修改docker-compose.yml文件,将节点1的连接配置进去。

    version: '3'
    services: 
      meitu_node_1:
        image: meitugeth
        container_name: meitu_node_1
        build: 
          context: ..
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303
        ports: 
          - 15450:8545
          - 15460:8546
          - 10303:30303
          - 10303:30303/udp
          - 10304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node1/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    
      meitu_node_2:
        image: meitugeth
        container_name: meitu_node_2
        build: 
          context: ..
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[172.20.0.2]:30303
        depends_on: 
          - meitu_node_1
        ports: 
          - 25450:8545
          - 25460:8546
          - 20303:30303
          - 20303:30303/udp
          - 20304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node2/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    
      meitu_node_3:
        image: meitugeth
        container_name: meitu_node_3
        build: 
          context: ..
        command: --ipcpath "/root/.ethereum/geth.ipc" --port 30303 --bootnodes enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[172.20.0.2]:30303
        depends_on: 
          - meitu_node_1
        ports: 
          - 45450:8545
          - 45460:8546
          - 40303:30303
          - 40303:30303/udp
          - 40304:30304/udp
        volumes: 
          - /etc/localtime:/etc/localtime
          - ~/data/meitu/node3/:/root/.ethereum/
        environment: 
          TZ: Asia/Shanghai
    

    这个时候,“主网”启动成功了!!!

    五、运行

    1. 解锁账户

    分别在3个节点上把validator无限期解锁,谁不解锁谁别出块、跳过你。
    这里源码默认10秒1块。
    进入geth JavaScript控制台后,执行以下命令:

    # 模板
    personal.unlockAccount(eth.validator,'名称',0)
    # 例子
    personal.unlockAccount(eth.validator,'jce001',0)
    personal.unlockAccount(eth.validator,'jce002',0)
    personal.unlockAccount(eth.validator,'jce003',0)
    

    根据美图解释,这里validatorcoinbase的区别:

    • coinbase:收取挖矿奖励
    • validator:可以设置为其他地址,但默认和coinbase一样。

    2. 启动挖矿

    进入geth JavaScript控制台后,执行以下命令:

    miner.start()
    

    3. 获取区块信息

    进入geth JavaScript控制台后,执行以下命令:

    # 模板
    eth.getBlock(区块编号)
    # 例子
    eth.getBlock(1)
    # 结果
    {
      coinbase: "0x849f9442198282fb21539351edb0378463e4c251",
      difficulty: 1,
      extraData: "0xd783010704846765746887676f312e392e37856c696e7578000000000000000084c3b20f15eb99c19bb8567d3a27a52947efb816f647cbc4491540ee5de685d54f4126d236f031d33312dfab1a5d7a895bbd4d154afd366b30918a9af6868ab300",
      gasLimit: 5237761,
      gasUsed: 0,
      hash: "0x30964585add8b4ef65529f38ebe00bb6581fc9ae7323327f7dfd666754de883b",
      logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
      mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
      nonce: "0x0000000000000000",
      number: 1,
      parentHash: "0x9390ffeae9812417704193667a0e106c8cd9e701217deb054737dab0325191d3",
      receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
      sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
      size: 794,
      stateRoot: "0xc5a87ecb7262f6c507f488f5f93efa27df81550f1e7691c1ab2093a4218d2ca0",
      timestamp: 1543990290,
      totalDifficulty: 131073,
      transactions: [],
      transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
      uncles: [],
      validator: "0x849f9442198282fb21539351edb0378463e4c251"
    }
    
    

    六、测试

    1. 获取账户余额

    # 获取账户
    eth.accounts
    
    # 获取余额
    eth.getBalance(账户地址)
    
    # 格式化长度
    web3.fromWei(数值)
    
    # 获取格式化后的余额,默认取第一个账户
    web3.fromWei(eth.getBalacne(eth.accounts[0]))
    
    # 获取指定账户余额
    web3.fromWei(eth.getBalance("0x849f9442198282fb21539351edb0378463e4c251"))
    

    2. 交易

    # 模板
    eth.sendTransaction({from: "发送者", to: "接受者", value: 数量})
    # 例子
    eth.sendTransaction({from: "0x849f9442198282fb21539351edb0378463e4c251", to: "0xc4118320f3d3c37a2ca8dad5c2f2a40f2a23ba02", value: 1000000000000000000})
    

    通过获取余额判断是否转账成功,也可通过返回的交易编号查询情况。

    web3.eth.getTransactionReceipt('0x8a4104da45c736c7a671ff7974b9b9a1848ff4c001f3cbcd4eb427aab50d604f')
    

    七、参考

  • 相关阅读:
    【2016-10-27】【坚持学习】【Day14】【VS 配置管理器 AssemblyInfo 】
    【2016-10-26】【坚持学习】【Day13】【WCF】【EF + Data Services】
    【2016-10-25】【坚持学习】【Day12】【WPF】【Telerik】【VirtualtionData 虚拟化数据】
    【2016-10-24】【坚持学习】【Day11】【WPF】【MVVM】
    【2016-10-20】【坚持学习】【Day10】【反射2】
    【2016-10-17】【坚持学习】【Day9】【反射】
    【2016-10-17】【坚持学习】【Day8】【抽象工厂模式】
    【2016-10-17】【坚持学习】【Day8】【工厂方法模式】
    【2016-10-17】【坚持学习】【Day8】【简单工厂模式】
    【2016-10-16】【坚持学习】【Day7】【建造者模式】
  • 原文地址:https://www.cnblogs.com/jianxuanbing/p/10071238.html
Copyright © 2011-2022 走看看