基于以太坊为女儿发一枚数字货币
基于以太坊为女儿发一枚数字货币
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信息,以便进行多节点互联静态配置文件的编写。
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语言进行以太坊智能合约调用的方法。