区块链学习实战01 - 以太坊初试
Windows以太坊安装(geth安装)
Geth,即go-ethereum是以太坊的客户端之一,是一个基于Go语言的客户端。以太坊还有别的客户端包括C++,JavaScript,python,Java等,比较常用的就是Go语言实现的客户端geth (go-ethereum),其他常用的还有一个叫testrpc的工具, 它使用了Python客户端pyethereum。
geth是一种CLI应用,它用Go语言编写,在主要的操作系统中都可使用。
对于Windows来说,geth是一个可执行文件。从官网下载windows版本的安装程序,运行即可(P.S. 由于安装程序修改系统环境变量,所以可能会被360拦下)。当然也可以通过源码安装。(前往官方安装教程)
检查是否安装成功:
私有链创始区块搭建
要创建私有网络,只需给出一个随机网络ID即可。可以简单使用--deth
标记运行一个私有网络,该网络允许多个与日志和调试相关的标记,而不用给出一个随机网络ID并放上多个与日志和调试相关的标记。
-
准备创世块文件
配置自己的创世块是为了区分公有链,同一个网络中,创世块必须是一样的,否则无法联通- 新建文本文档,输入如下内容并保存:
{ "config": { "chainId": 8, "homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0 }, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x40000", "extraData" : "", "gasLimit" : "0xffffffff", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "alloc": { } }
- 将上述文本文档重命名为
genesis.json
(个人实践时,如果直接用Notepad创建文件genesis.json
会发生编码错误),将文件移动到私有链的相应目录(例如:cd D:\geth\privatechain
)
- 新建文本文档,输入如下内容并保存:
-
创建数据存放地址并初始化创世块
- 运行CMD
- 进入该目录,例如:
cd D:\geth\privatechain
- 执行如下命令(创建数据存放地址并初始化创世块):
geth --datadir ./data/node0 init ./genesis.json
-
启动私有链,并进入JS控制台
geth --datadir ./data/node0 --networkid 314590 --ipcdisable --port 61910 --rpcport 8200 console
私有链节点的加入
-
创建另一个节点,打开一个新的CMD窗口
-
初始化创始区块(P.S. 同样先进入
D:\geth\privatechain
目录)geth --datadir ./data/node1 init ./genesis.json
-
启动新节点(注意使用不同的端口号
rpcport
和port
而使用同样的子网号networkid
,使用不同的identity
)geth --datadir ./data/node1 --networkid 314590 --ipcdisable --port 61911 --rpcport 8101 console
-
-
查看新创建的节点的信息,在JS控制台输入如下命令
admin.nodeInfo
-
加入新创建的节点
在node0的控制台中,输入如下命令,其中addPeer()
中的内容为node1的enode的内容admin.addPeer('enode://28fa4507a3c0612c706f47777007a9f763cdac2cd9569b9bf68eaddaacdcd5b85ead7e45c516c534787ee5c7697333eec21c4b5d1e17a45a1c1cdca8f0b67506@127.0.0.1:61911')
区块字段解读
在控制台输入命令eth.getBlock("latest")
获取最新一块区块的结构(也可指定区块号获取指定区块)
其各字段解读如下:
字段 | 含义 |
---|---|
difficulty | 挖矿难度 |
extraData | 与此区块相关的附加数据 |
gasLimit | 当前区块允许使用的最大gas |
gasUsed | 当前区块累计使用的gas |
hash | 区块的哈希值。如果区块没有被确认,这个字段会是null值 |
logsBloom | 区块日志的布隆过滤器,区块没被确认是值为null |
miner | 取得该区块记账权的矿工 |
mixHash | 一个Hash值,当与nonce组合时,证明此区块已经执行了足够的计算 |
nonce | PoW生成的哈希值 |
number | 区块号 |
parentHash | 前一个区块的哈希值 |
receiptsRoot | 收据树的根哈希值 |
sha3Uncles | 数据块的哈希值 |
size | 当前区块的字节大小 |
stateRoot | 区块状态树的根哈希 |
timestamp | 区块打包时的unix时间戳 |
totalDifficulty | 区块链到当前区块的总难度 |
transactions | 交易的对象 |
transactionsRoot | 区块的交易树的根哈希 |
uncles | 叔哈希的数组 |
日志输出分析
在以太坊的语境里,日志代表对事件的存储。通过命令eth.getTransactionReceipt()
可以获取日志。以下例子为在TA提供的网页(可能需要中大校园网才能访问)上的控制台中进行测试。
- 获取某一区块的结构,展开其交易的哈希值
复制其中一个交易的哈希值(例如transaction[5]),作为下一个调用方法的输入 - 获取交易收据
web3.eth.getTransactionReceipt("0xa9b49f7db4a11f0abda177053144c3cc1016ec89f35ad92dfcd44d8ed0f00e99")
- 展开其中的日志数组,可以看到如下结果
字段 | 含义 |
---|---|
address | 智能合约的地址 |
blockHash | 所在区块的哈希值 |
blockNumber | 所在区块号 |
data | 触发事件的时候传给事件的实际参数值 |
logIndex | 即本条日志在区块中记录的日志数组中的索引下标 |
topics数组 | 数组里的第一个数据元素就是事件的签名,其它数据元素就是被索引收录的事件参数值,所有在 topics 里的内容,都是被索引收录,可以通过 bloom filter 进行过滤的。 |
transactionHash | 交易哈希值 |
transactionIndex | 本交易在所属区块中所有打包交易数组中的索引下标 |
简单智能合约编写及部署运行
- 编写一个简单的加法合约,代码如下:
pragma solidity ^0.4.0;
contract TestContract
{
function sum(uint a, uint b) returns (uint)
{
return a + b;
}
}
-
启动私有链节点
-
通过命令
personal.newAccount("你的密码")
创建两个账户
-
挖矿获取一些以太币
账号创建后,还没有以太币,需要在私有链上挖矿,输入命令miner.start(1)
P.S.
miner
命令括号中的1
表示用一个线程进行挖矿,如果不配置,就会让CPU全速运行,影响计算机的使用。
运行一会后,主账号就会获取很多以太币,这个时候屏幕会快速刷屏
-
输入命令
miner.stop()
停止挖矿 -
查看账户余额
-
尝试进行一次交易,例如从账户0向账户1转账:
- 先解锁账户0
- 发送交易,账户 0 -> 账户 1
此时由于没有挖矿,用txpool.status
命令可以看到本地交易池中有一个待确认的交易,可以使用eth.getBlock("pending", true).transactions
查看当前待确认交易
- 使用下面命令开始挖矿:
miner.start(1);admin.sleepBlocks(1);miner.stop();
- 新区块挖出后,挖矿结束,查看账户1的余额,已经收到了账户0的以太币
P.S. 以太币的单位:
K w e i ( B a b b a g e ) = 1 0 3 W e i Kwei(Babbage) = 10^3 Wei Kwei(Babbage)=103Wei
M w e i ( L o v e l a c e ) = 1 0 6 W e i Mwei(Lovelace) = 10^6Wei Mwei(Lovelace)=106Wei
G w e i ( S h a n n o n ) = 1 0 9 W e i Gwei(Shannon) = 10^9Wei Gwei(Shannon)=109Wei
M i c r o e t h e r ( S z a b o ) = 1 0 12 W e i Microether(Szabo) = 10^{12}Wei Microether(Szabo)=1012Wei
M i l l i e t h e r ( F i n n e y ) = 1 0 15 W e i Milliether(Finney) = 10^{15}Wei Milliether(Finney)=1015Wei
E t h e r = 1 0 18 W e i Ether = 10^{18}Wei Ether=1018Wei
- 先解锁账户0
-
编译和部署智能合约
-
在Remix上编译调试智能合约代码
注意,选择的编译器版本要与代码中声明的编译器版本一致
编译成功 -
将智能合约部署到私有链上
因为我们要将该智能合约部署到私有链上,需要得到智能合约编译后的EVM二进制码Bytecode
和JSONABI
(Application Binary Interface)。将生成的交易保存到scenario.json
文件。在Remix上部署该交易,记录其input
回到Geth的控制台,用变量code
记录和abi
对应记录上面两个值,例如:code = "0x606060405260818060106000396000f360606040526000357c010000000000000000000000000000000000000000000000000000000090048063cad0899b146039576035565b6002565b34600257605a60048080359060200190919080359060200190919050506070565b6040518082815260200191505060405180910390f35b60008183019050607b565b9291505056" abi = [{"constant": false,"inputs": [{"name": "a","type": "uint256"},{"name": "b","type": "uint256"}],"name": "sum","outputs": [{"name": "","type": "uint256"}],"payable": false,"type": "function","stateMutability": "nonpayable"}]
-
使用账户0来部署合约,首先解锁账户
-
创建合约实例,发送部署合约的交易
输入:myContract = eth.contract(abi)
输入:contract=myContract.new({from:eth.accounts[0],data:code,gas:1000000})
此时如果没有挖矿,用txpool.status
命令可以看到本地交易池中有一个待确认的交易。使用miner.start(1);admin.sleepBlocks(1);miner.stop();
命令开始挖矿,一段时间后交易会被确认。
-
-
调用智能合约
-
使用以下命令通过发送交易来调用合约,
sendTransaction
方法的前几个参数应该与合约中的sum
方法的输入参数对应。这种情况下,交易会通过挖矿记录到区块链中(注意首先依然要先解锁账户)personal.unlockAccount(eth.accounts[0]) contract.sum.sendTransaction(2, 4, {from:eth.accounts[0]})
这时同样可以通过txpool.status
查看到本地交易池中有一个待确认的交易,也可以使用eth.getBlock("pending", true).transactions
查看当前待确认交易
-
在本地运行该方法可直接查看返回结果,不会记录到区块链中
contract.sum.call(2,4)
-
交易字段分析
前面已经讲到可以通过发送交易的方式调用智能合约,这里以该交易为例分析交易字段的含义:
字段 | 含义 |
---|---|
blockHash | 这个交易所在的块的哈希值 |
blockNumber | 这个交易所在的块的编号 |
from | 发起这个交易的账户或者用户 |
gas | 执行这个交易所需要的gas |
gasPrice | 当前gas与以太币换算的汇率(或者说gas的单价) |
hash | 当前这个交易的哈希值 |
input | 合约的16进制代码 |
nonce | 交易下的nonce值,是账户发起交易所维护的nonce,一个交易对应一个nonce值(P.S. 不同于PoW的nonce) |
r,v,s | 交易签名和用去决定交易的发送者的对应值 |
to | 交易接收者的地址(P.S. 创建合约时这一字段值为null) |
transactionIndex | 这个交易在其对应区块的序号 |
value | 交易要给交易接收者发送的以太币数量 |
参考资料
[1] 邹均,于斌,庄鹏,邢春晓.区块链核心技术与应用[M].北京:机械工业出版社,2018
[2] Narayan Prusty.区块链项目开发指南[M].北京:机械工业出版社,2018
[3] 前辈的博客
[4] 前辈的博客
[5] 前辈的博客
[6] 前辈的博客