美文网首页
基于以太坊为女儿发一枚数字货币

基于以太坊为女儿发一枚数字货币

作者: rzexin | 来源:发表于2018-11-19 12:08 被阅读0次

    基于以太坊为女儿发一枚数字货币

    0 写在前面

    家有2娃,姐妹相差3岁,姐姐刚上幼儿园,妹妹终于可在家愉快的玩耍了,不用再担心被姐姐抢玩具,或者由于姐姐的过度亲密,而被抓上一爪或咬上一口了:(

    言归正传,下面将通过给姐妹俩发一枚以她俩名字命名的XYC(Xuan Yuan Coin),来实践下:

    (1)基于Docker环境构建以太坊多节点私有网络

    (2)以太坊智能合约的开发、部署、调用

    (3)Go语言对以太坊智能合约的调用

    1 基于Docker的多节点私有网络搭建

    1.1 构建开发基础镜像

    该镜像包含开发常用工具和库

    1.1.1 编写Dockerfile文件

    FROM ubuntu:18.04
    RUN rm /bin/sh && ln -s /bin/bash /bin/sh
    RUN apt-get update
    RUN apt-get install -y wget 
    RUN mkdir /opt/java && \
        wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz -P /opt/java
     
    RUN tar zxvf /opt/java/jdk-8u181-linux-x64.tar.gz -C /opt/java &&\ 
        JAVA_HOME=/opt/java/jdk1.8.0_181 &&\ 
        sed -i '$a export PATH=$PATH' /root/.bashrc && \    
        sed -i "/^export PATH/i export JAVA_HOME=$JAVA_HOME" /root/.bashrc &&\ 
        sed -i "s%^export PATH.*$%&:$JAVA_HOME/bin%g" /root/.bashrc &&\ 
        source /root/.bashrc
     
    RUN apt-get install -y vim lsof wget tar bzip2 unzip passwd sudo net-tools rsync man git make automake cmake patch python python-pip telnet ruby inetutils-ping
     
    ENV GO_VERSION "1.11"
    RUN mkdir /opt/go && mkdir -p /workspace/go && \
            wget https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz -P /opt/go
    RUN tar -zxvf /opt/go/go$GO_VERSION.linux-amd64.tar.gz -C /opt/go 
    RUN GOPATH=/workspace/go && GOROOT=/opt/go/go && \
            sed -i "/^export PATH/i export GOROOT=$GOROOT" /root/.bashrc &&\ 
            sed -i "/^export PATH/i export GOPATH=$GOPATH" /root/.bashrc &&\ 
            sed -i "s%^export PATH.*$%&:$GOROOT/bin%g" /root/.bashrc && \
            source /root/.bashrc
     
    ENV TZ "Asia/Shanghai"
    RUN DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
    RUN echo $TZ > /etc/timezone
    RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime
    RUN dpkg-reconfigure tzdata -f noninteractive
    

    1.1.2 构建镜像

    $ sudo docker build -t js/ubuntu:dev -f js.Dockerfile .
    

    1.2 构建以太坊go-ethereum镜像

    1.2.1 编写创世区块文件

    准备该文件,用于创世区块的创建

    coinbase的内容是最想对女儿说的话:)

    {
        "nonce": "0x0000000000000042",
        "timestamp": "0x0",
        "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "extraData": "0x00",
        "gasLimit": "0x80000000",
        "difficulty": "0x1",
        "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "coinbase": "0x62616269206c6f7665207875616e267975616e0a",
        "alloc": {}, 
        "config": {}
    }   
    

    1.2.2 编写Dockerfile文件

    FROM js/ubuntu:dev
     
    ENV GETH_VERSION "1.8.15"
     
    RUN mkdir /opt/geth && \
            wget https://github.com/ethereum/go-ethereum/archive/v$GETH_VERSION.tar.gz -P /opt/geth/v$GETH_VERSION.tar.gz -C /opt/geth
            
    WORKDIR /opt/geth/go-ethereum-$GETH_VERSION
     
    ENV GOROOT=/opt/go/go
    ENV PATH=$PATH:$GOROOT/bin
    RUN make all 
    RUN sed -i "s%^export PATH.*$%&:/opt/geth/go-ethereum-$GETH_VERSION/build/bin%g" /root/.bashrc && \
            source /root/.bashrc
     
    ENV PATH=$PATH:/opt/geth/go-ethereum-$GETH_VERSION/build/bin
    RUN geth version
     
    ADD ./CustomGenesis.json /
    RUN geth --datadir '/opt/geth/datadir' init /CustomGenesis.json                         
    WORKDIR /opt/geth/datadir
    

    1.2.3 构建镜像

    $ sudo docker build -t js/geth:dev -f bc.Dockerfile .
    

    1.3 容器编排

    这里构建3个以太坊节点

    1.3.1 创建数据文件挂载目录

    这里挂载的数据文件目录中,存放以太坊节点的私钥、区块、cache等。

    $ sudo docker volume create --name=geth-peer1-datadir
    #geth-peer1-datadir
    $ sudo docker volume create --name=geth-peer2-datadir
    #geth-peer2-datadir
    $ sudo docker volume create --name=geth-peer3-datadir
    #geth-peer3-datadir
    

    1.3.2 编写docker-compose文件

    version: "3"
    
    services:
      peer1:
        image: js/geth:dev
        command: geth --datadir '/opt/geth/datadir' --ethash.dagdir '/opt/geth/datadir/ethash' --port 30303 --nodiscover --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --rpccorsdomain "*" --identity "peer1"
        container_name: peer1
        hostname: peer1
        ports: 
          - 30303:30303
          - 8545:8545
        expose:
          - "30303"
          - "8545"
        volumes:
          - geth-peer1-datadir:/opt/geth/datadir
        networks:
          bcnetwork:
            ipv4_address: 172.33.0.2
      peer2:
        image: js/geth:dev
        command: geth --datadir '/opt/geth/datadir' --ethash.dagdir '/opt/geth/datadir/ethash' --port 30303 --nodiscover --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --rpccorsdomain "*" --identity "peer2"
        container_name: peer2
        hostname: peer2
        ports: 
          - 40404:30303
          - 48545:8545
        expose:
          - "40404"
          - "48545"
        volumes:
          - geth-peer2-datadir:/opt/geth/datadir
        networks:
          bcnetwork:
            ipv4_address: 172.33.0.3
      peer3:
        image: js/geth:dev
        command: geth --datadir '/opt/geth/datadir' --ethash.dagdir '/opt/geth/datadir/ethash' --port 30303 --nodiscover --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --rpccorsdomain "*" --identity "peer3"
        container_name: peer3
        hostname: peer3
        ports: 
          - 50505:30303
          - 58545:8545
        expose:
          - "50505"
          - "58545"
        volumes:
          - geth-peer3-datadir:/opt/geth/datadir
        networks:
          bcnetwork:
            ipv4_address: 172.33.0.4
    
    networks:
      bcnetwork:
        driver: bridge
        ipam:
          config:
            - subnet: 172.33.0.0/16 
    
    volumes:
      geth-peer1-datadir:
        external: true
      geth-peer2-datadir:
        external: true
      geth-peer3-datadir:
        external: true
    

    1.3.3 geth参数说明

    command里面是执行的geth命令,参数很多,这里仅对使用到的进行介绍。

    geth --datadir '/opt/geth/datadir' --ethash.dagdir '/opt/geth/datadir/ethash' --port 30303 --nodiscover --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --rpccorsdomain "*" --identity "peer2"
    
    参数 含义
    datadir 数据库和keystore密钥的数据目录
    ethash.dagdir 存ethash DAGs目录
    port P2P发现监听端口
    nodiscover 禁用节点发现机制
    rpc 启用HTTP-RPC服务器
    rpcaddr HTTP-RPC服务器接口地址
    rpcport HTTP-RPC服务器监听端口
    rpcapi 基于HTTP-RPC接口提供的API
    rpccorsdomain 允许跨域请求的域名列表(逗号分隔)(浏览器强制)
    identity 自定义节点名

    1.3.4 特别说明

    为了避免节点被其他以太坊网络中的节点搜索到,使用了--nodiscover,关闭了自动发现,就需要通过配置静态节点文件的方式,手工进行节点的关联,为避免每次重启容器时IP重新分配而发生变化带来的不便,故在docker-compose文件的编写中,通过自定网络的方式,来固定IP。

    1.4 启动以太坊多节点网络

    1.4.1 启动命令

    $ sudo docker-compose -f docker-compose-geth.yaml up -d
    Creating peer1 ... done
    Creating peer3 ... done
    Creating peer2 ... done
    

    1.4.2 数据文件查看

    • 容器启动后,通过如下命令,可以查看到容器在本机的挂载目录:
    $ sudo docker inspect --format='{{json .Mounts}}' peer1 peer2 peer3 | jq
    [
      {
        "Type": "volume",
        "Name": "geth-peer1-datadir",
        "Source": "/var/lib/docker/volumes/geth-peer1-datadir/_data",
        "Destination": "/opt/geth/datadir",
        "Driver": "local",
        "Mode": "rw",
        "RW": true,
        "Propagation": ""
      }
    ]
    [
      {
        "Type": "volume",
        "Name": "geth-peer2-datadir",
        "Source": "/var/lib/docker/volumes/geth-peer2-datadir/_data",
        "Destination": "/opt/geth/datadir",
        "Driver": "local",
        "Mode": "rw",
        "RW": true,
        "Propagation": ""
      }
    ]
    [
      {
        "Type": "volume",
        "Name": "geth-peer3-datadir",
        "Source": "/var/lib/docker/volumes/geth-peer3-datadir/_data",
        "Destination": "/opt/geth/datadir",
        "Driver": "local",
        "Mode": "rw",
        "RW": true,
        "Propagation": ""
      }
    ]
    
    • 进入目录后,可以查看到对应的文件内容:
    /var/lib/docker/volumes/geth-peer1-datadir/_data/
    ├── geth
    │   ├── chaindata
    │   │   ├── 000002.ldb
    │   │   ├── 000003.log
    │   │   ├── CURRENT
    │   │   ├── CURRENT.bak
    │   │   ├── LOCK
    │   │   ├── LOG
    │   │   └── MANIFEST-000004
    │   ├── lightchaindata
    │   │   ├── 000001.log
    │   │   ├── CURRENT
    │   │   ├── LOCK
    │   │   ├── LOG
    │   │   └── MANIFEST-000000
    │   ├── LOCK
    │   ├── nodekey
    │   └── transactions.rlp
    ├── geth.ipc
    └── keystore
    

    1.4.3 启动日志查看

    以peer1为例:

    $ sudo docker-compose -f docker-compose-geth.yaml logs -f peer1
    Attaching to peer1
    peer1    | INFO  Maximum peer count                       ETH=25 LES=0 total=25
    peer1    | INFO  Starting peer-to-peer node               instance=Geth/v1.8.15-stable/linux-amd64/go1.11
    peer1    | INFO  Allocated cache and file handles         database=/opt/geth/datadir/geth/chaindata cache=768 handles=1024
    peer1    | INFO  Initialised chain configuration          config="{ChainID: <nil> Homestead: <nil> DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: <nil> EIP158: <nil> Byzantium: <nil> Constantinople: <nil> Engine: unknown}"
    peer1    | INFO  Disk storage enabled for ethash caches   dir=/opt/geth/datadir/geth/ethash count=3
    peer1    | INFO  Disk storage enabled for ethash DAGs     dir=/root/.ethash                 count=2
    peer1    | INFO  Initialising Ethereum protocol           versions="[63 62]" network=1
    peer1    | INFO  Loaded most recent local header          number=0 hash=eef349…8aeba8 td=1
    peer1    | INFO  Loaded most recent local full block      number=0 hash=eef349…8aeba8 td=1
    peer1    | INFO  Loaded most recent local fast block      number=0 hash=eef349…8aeba8 td=1
    peer1    | INFO  Regenerated local transaction journal    transactions=0 accounts=0
    peer1    | INFO  Starting P2P networking 
    peer1    | INFO  IPC endpoint opened                      url=/opt/geth/datadir/geth.ipc
    peer1    | INFO  RLPx listener up                         self="enode://bdf07fa1c2aa550005e13fd68b1ab0ccb1026669d93da1aee430124277f5e76f4f116b45c4dcefada9ff23b8fe89c2b8c2bab6e4b3c5ec92447164c5b06c18bf@[::]:30303?discport=0"
    

    1.4.4 各节点enode信息

    记录下各个节点的enode信息,以便进行多节点互联静态配置文件的编写。

    节点名 enode
    peer1 enode://bdf07fa1c2aa550005e13fd68b1ab0ccb1026669d93da1aee430124277f5e76f4f116b45c4dcefada9ff23b8fe89c2b8c2bab6e4b3c5ec92447164c5b06c18bf@[::]:30303
    peer2 enode://ae02f18ad58e96a309761e85df8dbceef6c5efe47facc10608ac4e0b002cf3901222a389b44a169bf09058c2b17cebe3d0f96078ebe58c6d52801a800b6b540f@[::]:30303
    peer3 enode://1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52@[::]:30303

    1.5 帐号创建

    以peer1为例

    1.5.1 进入peer1容器

    $ sudo docker exec -it peer1 bash
    root@peer1:/opt/geth/datadir# 
    
    

    1.5.2 attach进节点

    root@peer1:/opt/geth/datadir# geth attach ./geth.ipc 
    Welcome to the Geth JavaScript console!
    
    instance: Geth/v1.8.15-stable/linux-amd64/go1.11
     modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
    

    1.5.3 创建帐号

    括号中是帐号解锁密码

    > personal.newAccount("123456")
    "0x8330c2a5c1399af554fbb5c231c696525f6c0ea9"
    

    1.5.4 备份钱包私钥文件

    创建帐号后,在主机挂载目录或容器指定的数据目录中均能看到生成的钱包私钥文件,需要备份好改文件,若丢失该文件,该钱包里的以太币及其他token就都玩儿完了:(

    /var/lib/docker/volumes/geth-peer1-datadir/_data/keystore/
    └── UTC--2018-09-13T08-17-21.665798564Z--8330c2a5c1399af554fbb5c231c696525f6c0ea9
    
    /opt/geth/datadir/keystore/
    `-- UTC--2018-09-13T08-17-21.665798564Z--8330c2a5c1399af554fbb5c231c696525f6c0ea9
    

    1.5.5 各节点帐号信息

    同样在其他2个节点创建帐号

    这里仅在每个节点上创建了一个账号,当然你可以在每个节点上创建任意多个账号

    节点名 帐号 IP
    peer1 0x8330c2a5c1399af554fbb5c231c696525f6c0ea9 172.33.0.2
    peer2 0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95 172.33.0.3
    peer3 0x562433b23bf24060db587f8c1ef288ac57eab2b9 172.33.0.4

    1.6 节点关联

    1.6.1 关联拓扑

    因为节点启动时,使用了--nodiscover,关闭了自动发现,故需要手工进行关联。

    节点手工关联有多种方式,这里只介绍两种:命令式和配置式

    1.6.2 命令式节点关联

    peer1

    • 执行命令
    web3.admin.addPeer('enode://ae02f18ad58e96a309761e85df8dbceef6c5efe47facc10608ac4e0b002cf3901222a389b44a169bf09058c2b17cebe3d0f96078ebe58c6d52801a800b6b540f@172.33.0.3:30303')
    
    web3.admin.addPeer('enode://1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52@172.33.0.4:30303')
    
    • 查看连接
    > web3.admin.peers
    [{
        caps: ["eth/62", "eth/63"],
        id: "1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: false,
          localAddress: "172.33.0.2:33674",
          remoteAddress: "172.33.0.4:30303",
          static: true,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 68609515,
            head: "0x649bd5691fa63397dbf2040fdd839802cdeb12060da7eb9e7021093b7ba4f3aa",
            version: 63
          }
        }
    }, {
        caps: ["eth/62", "eth/63"],
        id: "ae02f18ad58e96a309761e85df8dbceef6c5efe47facc10608ac4e0b002cf3901222a389b44a169bf09058c2b17cebe3d0f96078ebe58c6d52801a800b6b540f",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: false,
          localAddress: "172.33.0.2:52858",
          remoteAddress: "172.33.0.3:30303",
          static: true,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 84596986,
            head: "0x3735f8a9e9c572dc23e2013f8aed439f8e5494008d3403161922858fa19a929c",
            version: 63
          }
        }
    }]
    

    peer2

    peer2已于peer1相连,只需要关联peer3即可

    • 执行命令
    web3.admin.addPeer('enode://1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52@172.33.0.4:30303')
    
    • 查看连接
    > web3.admin.peers
    [{
        caps: ["eth/62", "eth/63"],
        id: "1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: false,
          localAddress: "172.33.0.3:54994",
          remoteAddress: "172.33.0.4:30303",
          static: true,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 84765477,
            head: "0x0f763d91b688dec472e0887f7993caae36030c75c9c7a271e8a1ffe3c1254066",
            version: 63
          }
        }
    }, {
        caps: ["eth/62", "eth/63"],
        id: "bdf07fa1c2aa550005e13fd68b1ab0ccb1026669d93da1aee430124277f5e76f4f116b45c4dcefada9ff23b8fe89c2b8c2bab6e4b3c5ec92447164c5b06c18bf",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: true,
          localAddress: "172.33.0.3:30303",
          remoteAddress: "172.33.0.2:52858",
          static: false,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 84596986,
            head: "0x3735f8a9e9c572dc23e2013f8aed439f8e5494008d3403161922858fa19a929c",
            version: 63
          }
        }
    }]
    

    peer3

    无需执行命令,在peer1和peer2上已经建立了关联

    • 查看连接
    > web3.admin.peers
    [{
        caps: ["eth/62", "eth/63"],
        id: "ae02f18ad58e96a309761e85df8dbceef6c5efe47facc10608ac4e0b002cf3901222a389b44a169bf09058c2b17cebe3d0f96078ebe58c6d52801a800b6b540f",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: true,
          localAddress: "172.33.0.4:30303",
          remoteAddress: "172.33.0.3:54994",
          static: false,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 84765477,
            head: "0x0f763d91b688dec472e0887f7993caae36030c75c9c7a271e8a1ffe3c1254066",
            version: 63
          }
        }
    }, {
        caps: ["eth/62", "eth/63"],
        id: "bdf07fa1c2aa550005e13fd68b1ab0ccb1026669d93da1aee430124277f5e76f4f116b45c4dcefada9ff23b8fe89c2b8c2bab6e4b3c5ec92447164c5b06c18bf",
        name: "Geth/v1.8.15-stable/linux-amd64/go1.11",
        network: {
          inbound: true,
          localAddress: "172.33.0.4:30303",
          remoteAddress: "172.33.0.2:33674",
          static: false,
          trusted: false
        },
        protocols: {
          eth: {
            difficulty: 84596986,
            head: "0x3735f8a9e9c572dc23e2013f8aed439f8e5494008d3403161922858fa19a929c",
            version: 63
          }
        }
    }]
    

    1.6.3 配置式节点关联

    分别在peer1和peer2指定的数据文件目录datadir(/opt/geth/datadir)中,创建静态配置文件static-nodes.json,亦即在docker本地映射目录/var/lib/docker/volumes/geth-peerX-datadir/_data中去创建

    /var/lib/docker/volumes/geth-peer1-datadir/_data]# cat static-nodes.json
    [
    "enode://ae02f18ad58e96a309761e85df8dbceef6c5efe47facc10608ac4e0b002cf3901222a389b44a169bf09058c2b17cebe3d0f96078ebe58c6d52801a800b6b540f@172.33.0.3:30303",
    "enode://1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52@172.33.0.4:30303"
    ]
    
    /var/lib/docker/volumes/geth-peer2-datadir/_data]# cat static-nodes.json
    [
    "enode://1294e59212c3b80d731040a1a1aa9a21efe2aaccbb1f01978ad717494ef091a92a04e206d1e39bd80f0ab80043f4bd0998c7728af4ca0d5eabfa11736f162b52@172.33.0.4:30303"
    ]
    

    节点启动后,便会自动加载静态配置文件,进行指定节点的关联,连接关系同上,这里就省略展示。

    1.7 以太坊geth客服端的基本操作

    1.7.1 启动挖矿

    开启挖矿后,方能实现合约的部署及转账等操作

    start括号里面的值代表线程数

    若需要停止挖矿,使用命令:miner.stop()

    > miner.start(1)
    null
    

    1.7.2 查看余额

    挖一会儿后,便能通过以下命令查看到账户以太余额

    • peer1
    > web3.fromWei(eth.getBalance(eth.accounts[0]))
    1890
    
    • peer2
    > web3.fromWei(eth.getBalance(eth.accounts[0]))
    1325
    

    1.7.3 转账

    以peer1的帐号向peer2的帐号转账1000个以太币为例

    > personal.unlockAccount(eth.accounts[0])
    Unlock account 0x8330c2a5c1399af554fbb5c231c696525f6c0ea9
    Passphrase: 
    true
    
    > eth.sendTransaction({from:eth.accounts[0], to:"0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95", value:web3.toWei(1000, "ether")})
    "0xb672882e6fa63be9c0e7d5973bcb3dff054bdd759a0821c7b77eb54c63ff1806"
    
    • 注:转账之前,需先对帐号解锁,否则报错:
    > eth.sendTransaction({from:eth.accounts[0], to:"0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95", value:web3.toWei(1000, "ether")})
    Error: authentication needed: password or unlock
        at web3.js:3143:20
        at web3.js:6347:15
        at web3.js:5081:36
        at <anonymous>:1:1
    

    待挖矿确认后,可查看到账户的变化:

    注:实际上账户金额,已不止这么多,因为有新的区块产生,为了便于理解,对数值做了下调整

    • peer1
    > web3.fromWei(eth.getBalance(eth.accounts[0]))
    890
    
    • peer2
    > web3.fromWei(eth.getBalance(eth.accounts[0]))
    2325
    

    2 ERC20代币合约编写

    2.1 ERC20标准介绍

    2.1.1 为什么需要ERC20?

    目的是为统一合约标准,以利于交易所或钱包开发者支持你的代币发行及储存,否则交易所及钱包开发者就需要再写一份自定义代码来适配你的智能合约。

    2.1.2 ERC20标准

    ERC20定义了一些标准的接口函数:balanceOf 、 totalSupply 、transfer 、transferFrom 、approve和allowance 。 以及一些可选的字段,例如通证名称、符号以及小数保留位数等。

    详见:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

    2.1.3 ERC20接口

    contract ERC20 {
       function totalSupply() constant returns (uint theTotalSupply);
       function balanceOf(address _owner) constant returns (uint balance);
       function transfer(address _to, uint _value) returns (bool success);
       function transferFrom(address _from, address _to, uint _value) returns (bool success);
       function approve(address _spender, uint _value) returns (bool success);
       function allowance(address _owner, address _spender) constant returns (uint remaining);
       event Transfer(address indexed _from, address indexed _to, uint _value);
       event Approval(address indexed _owner, address indexed _spender, uint _value);
    }
    
    • 功能介绍:
    函数名 功能
    totalSupply 返回存在于流通中的通证(Token)总量
    balanceOf 返回指定账户地址的通证余额
    transfer 让调用方将指定数量的通证发送到另一个地址,即转账
    transferFrom 允许智能合约自动执行转账流程并代表所有者发送给定数量的通证
    approve 调用方授权给定的地址可以从其地址中提款
    allowance 返回被允许转移的余额数量
    event Transfer 事件通知,当token被转移时,必须调用触发,类似回调,当事件发生时,会得到通知
    event Approval 事件通知,当任何成功调用approve后,必须调用触发

    2.2 代币合约编写与执行

    2.2.1 代币属性

    属性
    代币名称 XuanYuan Coin(萱媛币)
    代号 xyc
    发行量 406647615087857(俩妞8位生日相乘)

    2.2.2 合约编写

    使用在线编辑器:http://remix.ethereum.org

    直接附上完整合约代码,限于篇幅,具体合约代码实现的细节就不展开介绍,比较简单的实现,相信也比较容易看明白。

    pragma solidity ^0.4.25;
    
    contract ERC20 {
       function totalSupply() public constant returns (uint theTotalSupply);
       function balanceOf(address _owner) public constant returns (uint balance);
       function transfer(address _to, uint _value) public returns (bool success);
       function transferFrom(address _from, address _to, uint _value) public returns (bool success);
       function approve(address _spender, uint _value) public returns (bool success);
       function allowance(address _owner, address _spender) public constant returns (uint remaining);
       event Transfer(address indexed _from, address indexed _to, uint _value);
       event Approval(address indexed _owner, address indexed _spender, uint _value);
    }
    
    contract xyc is ERC20 {
        string name = "XuanYuan Coin";
        string symbol = "xyc";
        uint private xyTotalSupply;
        mapping(address=>uint) balances;
        mapping(address=>mapping(address=>uint)) allowances;
        address public owner;
        
        constructor(address _owner, uint _total) public {
            owner = _owner;
            xyTotalSupply = _total;
            balances[owner] = _total;
        }
        
       function totalSupply() public constant returns (uint theTotalSupply) {
           theTotalSupply = xyTotalSupply;
           return theTotalSupply;
       }
       
       function balanceOf(address _owner) public constant returns (uint balance) {
           return balances[_owner];
       }
       
       function transfer(address _to, uint _value) public returns (bool success) {
           require(_to != address(0));
           require(_value <= balances[msg.sender]);
           require(balances[_to] + _value >= balances[_to]);
           
           balances[msg.sender] -= _value;
           balances[_to] += _value;
           emit Transfer(msg.sender, _to, _value);
           
           return true;
       }
       
       function transferFrom(address _from, address _to, uint _value) public returns (bool success) {
           require(_to != address(0));
           require(_value <= balances[_from]);
           require(_value <= allowances[_from][msg.sender]);
           require(balances[_to] + _value >= balances[_to]);
           
           balances[_from] -= _value;
           balances[_to] += _value;
           
           allowances[_from][msg.sender] -= _value;
           emit Transfer(_from, _to, _value);
           
           return true;
       }
       
       function approve(address _spender, uint _value) public returns (bool success) {
           require(_value <= balances[msg.sender]);
           allowances[msg.sender][_spender] = _value;
           emit Approval(msg.sender, _spender, _value);
           return true;
       }
       
       function allowance(address _owner, address _spender) public constant returns (uint remaining) {
           return allowances[_owner][_spender];
       }
       
       event Transfer(address indexed _from, address indexed _to, uint _value);
       event Approval(address indexed _owner, address indexed _spender, uint _value);
    }
    

    2.2.3 合约编译

    • 运行环境选择Web3 Provider
    • 连接任意一个私有节点
    • 切换到Compile页,点击Start to compile按钮,可以勾选Auto compile以便在保存时就自动编译:

    2.3.4 解锁账户

    首要的是开启挖矿,和账户解锁,方能进行合约的部署和执行

    > miner.start(1)
    null
    
    > personal.unlockAccount(eth.accounts[0], "123456", 1000000)
    true
    

    2.3.5 合约部署

    • 选择要部署的合约,填写构造方法中的所有人地址和发行量后,点击transact
    • 部署日志输出
    • 查看到已部署好的合约

    2.3 智能合约的基本操作

    2.3.1 查看所有者账户XYC余额

    2.3.2 向peer2的一个账号转账

    • 查看peer2上的帐号
    > eth.accounts
    ["0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95"]
    
    • 当前该帐号XYC余额为0
    • 执行转账操作
    • 再次查看余额,peer2的这个账户已经多了个2018个XYC了

    3 使用Go语言调用智能合约实践

    3.1 将智能合约编译成Go源码

    目的是为生成智能合约各个方法的Go语言实现接口

    • 步骤示意
    • 在remix中拷贝合约的abi文件,存入xyc.abi文件中
    • 执行命令
    $ abigen --abi xyc.abi --pkg xyc --type xyc --out xyc.go
    
    参数 含义
    pkg 指定编译成go文件的package名称
    abi 指定abi文件,该文件在remix部署合约后可得到
    type go文件的入口函数,可以认为是类名
    out 指定输出的go文件名
    • 关键代码片段
    // Xyc is an auto generated Go binding around an Ethereum contract.
    type Xyc struct {
        XycCaller     // Read-only binding to the contract
        XycTransactor // Write-only binding to the contract
        XycFilterer   // Log filterer for contract events
    }  
    
    // NewXyc creates a new instance of Xyc, bound to a specific deployed contract.
    func NewXyc(address common.Address, backend bind.ContractBackend) (*Xyc, error) {
        contract, err := bindXyc(address, backend, backend, backend)
        if err != nil {
            return nil, err 
        }   
        return &Xyc{XycCaller: XycCaller{contract: contract}, XycTransactor: XycTransactor{contract: contract}, XycFilterer: XycFilterer{contract:   contract}}, nil
    }     
    

    3.2 新建账户

    在用Go调用智能合约前,我们先看看如何创建账户

    package main
    
    import (
        "github.com/ethereum/go-ethereum/rpc"
        "fmt"
        "log"
    )
    
    func newAccount(pass string)  {
        cli, err := rpc.Dial("http://localhost:8545")
        if err != nil {
            log.Fatal("failed to conn to eth", err)
        }
    
        var accid string
        cli.Call(&accid, "personal_newAccount", pass)
    
        fmt.Println(accid)
        //0x503f332e28c0eb56f456aa05ded6f52a9d45a868
    }
    
    func main() {
        newAccount("123456")
    }
    
    • 在peer1上查看账号新增帐号
    > eth.accounts
    ["0x8330c2a5c1399af554fbb5c231c696525f6c0ea9", "0x503f332e28c0eb56f456aa05ded6f52a9d45a868"]
    

    3.3 查询账户余额

    package main
    
    import (
        "fmt"
        "log"
        "XYC/xyc"
        "os"
        "math/big"
        "github.com/ethereum/go-ethereum/common"
        "github.com/ethereum/go-ethereum/rpc"
        "github.com/ethereum/go-ethereum/ethclient"
        "github.com/ethereum/go-ethereum/accounts/abi/bind"
    )
    
    var contractAddr = "0xaf959e95fb23c666c05e374290517b4723b847d6"
    
    func getBalance(accid string) {
        cli, err := ethclient.Dial("http://localhost:8545")
        if err != nil {
            log.Fatal("failed to conn to eth", err)
        }
    
        token, err := xyc.NewXyc(common.HexToAddress(contractAddr), cli)
        if err != nil {
            log.Fatal("get contract err", err)
        }
    
        val, err := token.BalanceOf(nil, common.HexToAddress(accid))
        if err != nil {
            log.Fatal("get balance err", err)
        }
        fmt.Println("get val:", val)
    }
    
    func main() {
        getBalance("0x8330c2a5c1399af554fbb5c231c696525f6c0ea9")
        getBalance("0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95")
    }
    //OUTPUT:
    //get val: 406647615085839
    //get val: 2018
    

    3.3 转账操作

    节点2 a5c账号有2018XYC,给节点3 562账号转1000XYC

    比较关键的是要打开钱包文件以授权转账

    package main
    
    import (
        "fmt"
        "log"
        "XYC/xyc"
        "os"
        "math/big"
        "github.com/ethereum/go-ethereum/common"
        "github.com/ethereum/go-ethereum/rpc"
        "github.com/ethereum/go-ethereum/ethclient"
        "github.com/ethereum/go-ethereum/accounts/abi/bind"
    )
    
    var contractAddr = "0xaf959e95fb23c666c05e374290517b4723b847d6"
    
    func transfer(from, to, pass string, val int64) {
        cli, err := ethclient.Dial("http://localhost:8545")
        if err != nil {
            log.Fatal("err to connect geth")
        }
        token, err := xyc.NewXyc(common.HexToAddress(contractAddr), cli)
        if err != nil {
            log.Fatal("get contract err", err)
        }
    
        y, err := token.BalanceOf(nil, common.HexToAddress(from))
        if err != nil {
            log.Fatal("get balance err", err)
        }
    
        fmt.Println("get val:", y)
    
        fd, err := os.Open("/var/lib/docker/volumes/geth-peer2-datadir/_data/keystore/UTC--2018-09-13T08-26-10.647670256Z--a5cfb51caede4c8fb2634d88e6ac860bfa048c95")
        auth, err := bind.NewTransactor(fd, pass)
        if err != nil {
            log.Fatal("get NewTransactor err", err)
        }
    
        _, err = token.Transfer(auth, common.HexToAddress(to), big.NewInt(val))
        if err != nil {
            log.Fatal(" Transfer err", err)
        }
    }
    
    func main() {
        transfer("0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95",
            "0x562433b23bf24060db587f8c1ef288ac57eab2b9",
            "123456", 1000)
    }
    
    • 执行前:
    归属节点 账号 XYC
    peer2 0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95 2018
    peer3 0x562433b23bf24060db587f8c1ef288ac57eab2b9 0
    • 执行后:

    可见peer2的a5c帐号减少1000XYC,peer3的562帐号增加1000XYC,转账完成。

    归属节点 账号 XYC
    peer2 0xa5cfb51caede4c8fb2634d88e6ac860bfa048c95 1018
    peer3 0x562433b23bf24060db587f8c1ef288ac57eab2b9 1000

    4 写在后面

    本文通过为女儿发一枚数字货币,准确说是基于以太坊ERC20标准的通证(Token),以此来介绍了基于Docker环境搭建以太坊多节点私有网络的过程,以及如何通过remix网页工具编写solidity代币智能合约,及其部署和调用的方法,最后介绍了通过Go语言进行以太坊智能合约调用的方法。

    相关文章

      网友评论

          本文标题:基于以太坊为女儿发一枚数字货币

          本文链接:https://www.haomeiwen.com/subject/tavofqtx.html