区块链热身报告

区块链project热身报告

安装以太坊

Geth是以太坊智能合约开发中最常用的工具,执行在GO上运行的完整以太坊节点,通过Geth,我们可以实现以太坊的各种功能,如账户的新建、编辑、删除,开启挖矿,以太币的转移,智能合约的部署和执行等等。

windows下安装

官网下载安装包即可(需翻墙),安装过程会自动配置环境变量,在cmd中使用geth version可以查看是否安装成功

ubuntu下安装

同样去官网下载对应版本,这是一个tar.gz包,下载后解压即可得到geth可执行文件,配置好环境变量就可以使用了

私有链搭建

进入到为搭建私有链创建的文件夹privatechain后,执行以下操作

配置文件

要搭建私有链,首先需要编写创始区块配置文件,命名为genesis.json,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0xffffffff",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}

要注意几点:

  • chainId不能为0,否则部署智能合约时会出错:

    1
    invalid sender undefined
  • gaslimit不能太小,否则部署智能合约时会报错:

    1
    Error: exceeds block gas limit undefined
  • difficulty会影响到你节点的挖矿速度

初始化私有链

1
geth --datadir data0 init genesis.json

这条命令表示初始化私有链到文件夹data0中,这条链的数据存放在data0里,并根据genesis.json中的内容把创世区块写入区块链,通过log信息中的Successfully wrote genesis state我们知道初始化成功。

初始化后的初始化成功后的目录如下:

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

启动私有链节点

1
geth --datadir data0 --networkid 1108 console

geth console,表示启动节点并进入交互式控制台,控制台是一个交互式的Javascript执行环境

–-datadir选项指定使用data0作为数据目录,即运行我们前面初始化的那条私有链

–-networkid选项指定这个私有链的网络id为1108。网络id在连接到其他节点的时候会用到,以太坊公网的网络id是1,为了不与公有链网络冲突,运行私有链节点的时候要指定自己的网络id

一些简单操作

查看账户

1
>eth.accounts

创建账户

1
2
3
>personal.newAccount()
> Passphrase:
> Repeat passphrase:

查看账户余额

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

启动&停止挖矿

1
2
> miner.start(10)#参数10表示挖矿使用的线程数
> miner.stop():

解锁账户

1
> personal.unlockAccount(eth.accounts[0])

发送交易

1
2
> amount = web3.toWei(10,'ether')
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})#这个交易表示从账户0向账户1转账10个以太币

私有链节点加入

要向私有链中加入节点,我们需要同时运行两个节点,同时启动节点的命令是有要求的,我启动两个节点(同一台电脑)的命令分别为:

1
geth --networkid 1108 --nodiscover --datadir data0 --port 30303 --rpc --rpcapi net,eth,web3,personal --rpcport 8545 --rpcaddr localhost --verbosity 6 console 2>>geth0.log --ipcpath "geth0.rpc"
1
geth --networkid 1108 --nodiscover --datadir data1 --port 55554 --rpc --rpcapi net,eth,web3,personal --rpcport 8101 --rpcaddr localhost --verbosity 6  console 2>>geth1.log --ipcpath "geth1.rpc"

当然,首先得搭建两个节点data0和data1.

参数解释:

1
2
3
4
5
6
7
8
9
--nodiscover 关闭p2p网络的自动发现,需要手动添加节点,这样有利于我们隐藏私有网络
--datadir 区块链数据存储目录
--port 网络监听端口,默认30303
--networkid 网络标识,私有链取一个大于4的随意的值
--rpc 启用ipc服务
--rpcport ipc服务端口,默认端口号8545
--rpcapi 表示可以通过ipc调用的对象
--rpcaddr ipc监听地址,默认为127.0.0.1,只能本地访问
console 打开一个可交互的javascript环境

这之中要注意的几个地方是:

  • 端口号必须不同
  • rpc端口号必须不同
  • ipcpath必须不同

进入了Javascript Console之后,首先在第一个节点data0那执行命令查看enode:

1
admin.nodeInfo.enode

然后在第二个节点data1那执行命令

1
admin.addPeer("xxx")

括号内填查看到的data0的enode信息,返回true即可成功加入节点,加入后data1就会自动开始同步data0的所有区块。

之后可以使用以下命令来查看节点是否添加成功:

1
2
net.peerCount
admin.peers

在data1使用eth.blockNumber来查看区块是否同步成功。

注意:

  1. windows下节点同步可能会失败,所以最好在Linux下进行加入节点的操作
  2. 要保证两个节点在同一个区块链上工作的话首先要保证genesis创世区块链是一样的,所以注意使用同样的genesis.json文件来创建.

解释getBlock字段

使用getBlock来得到区块相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 eth.getBlock(0)
{
difficulty: 131072,
extraData: "0x",
gasLimit: 4294967295,
gasUsed: 0,
hash: "0x04786260f9e2b8a341a6a07949d74365c53bc4fd1edb00ff3b5f209f86906579",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000042",
number: 0,
parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 508,
stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
timestamp: 0,
totalDifficulty: 131072,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}

参数为0,查看的是创世区块,也可以把参数设为”latest”来查看最新区块。

  • difficulty:当前块的难度。
  • extraData:包含这个区块的任意字节相关数据。
  • gasLimit:当前区块允许使用的最大gas,这个值是我们genesis.json中的gasLimit值。
  • gasUsed:当前区块累计使用的总的gas。
  • hash:区块的哈希串。当这个区块处于pending将会返回null。
  • logsBloom:由日志信息组成的一个Bloom过滤器 (数据结构)。当这个区块处于pending将会返回null。
  • miner:这个区块获得奖励的矿工。
  • mixHash:一个Hash值,当与nonce组合时,证明此区块已经执行了足够的计算
  • nonce:在挖矿过程中起作用,POW生成的哈希。当这个区块处于pending将会返回null。
  • number:区块号。当这个区块处于pending将会返回null。
  • parentHash:父区块的哈希值。由于这是创始区块,因此该值为0。
  • receiptsRoot:包含此区块所列的所有交易收据的树的根节点Hash值
  • sha3Uncles:叔区块的哈希值。
  • size:当前这个块的字节大小。
  • stateRoot:区块的最终状态前缀树的根。
  • timestamp:区块打包时的unix时间戳。
  • totalDifficulty:区块链到当前块的总难度。
  • transactions:交易对象。或者是32字节的交易哈希。
  • transactionsRoot:包含此区块所列的所有交易的树的根节点Hash值
  • uncles:叔哈希的数组。

叔块是什么?

在上面的区块中有叔块的信息,那么叔块是什么呢?

我们都知道在挖矿过程中,只有最长的那条链才会被所有节点所承认,挖到在最长链的那些区块的矿工才能获得奖励。如果一个块不是最长链的一部分,那么它被称为是“孤块”。在比特币中,孤块没有意义,随后将被抛弃,发现这个孤块的矿工也拿不到采矿相关的奖励。

根据Ethereum的GHOST协议,不认为孤块没有价值,而是会给与发现孤块的矿工以回报。在以太坊中,孤块被称为“叔块”(uncle block),它们可以为主链的安全作出贡献。

由于GHOST协议支付报酬给叔块,这激励了矿工在新发现的块中去引用叔块。引用叔块使主链更重。在比特币,最长的链是主链。在以太坊中,主链是指最重的链

解释日志输出

通过在启动私有链节点时在命令最后加上2 >>xxx.log即可把日志输出到log文件中,通过设置选项--verbosity n可以设置日志的程度。

我选取了一些日志进行解释:

日志1

这个日志是我在给私有链添加节点时的日志输出,可以看到,执行命令后的第一个日志输出就是Adding p2p peer,表示加入节点。connnection set up表示建立连接,之后直到Ethereum peer connected,连接成功。然后就要开始为节点上区块链的同步做准备了,Block synchronisation started表示区块同步开始。此后要经过一系列的准备工作,比如获取链高度、下载区块体、获取区块头…最后可以看到一连串的Inserted new block,这就表示同步区块正式开始啦,可以从右边的number得知同步进度。

日志2

这个日志是挖矿的日志。一开始的Updated mining threads表示更新挖矿的线程,右边可以看到线程数量为10,这是在miner.start(10)中设置的。Etherbase automatically configured表示使用默认的coinbase,默认的是本地账户中的第一个账户,即eth.accounts[0],挖矿所得到的所有奖励都会进入这个账户中。之后Commit new mining work表示挖矿正式开始,那个🔨开头的日志说明成功挖到了矿,可以从右边的number看到挖到区块的数目。

日志3

这个简短的日志是进行一笔交易的日志,不知道为啥设置verbosity为6也还是看不到更具体的信息。Pooled new future transaction表示提交到交易缓冲池,Promoting queued transaction表示选择一部分交易进入pending队列进行处理,Submitted transaction表示交易被成功处理并提交,最后的Broadcast transaction表示把这笔交易广播出去。

部署智能条约

编写合约

首先编写智能合约,用solidity语言实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
contract Mortal {
/* Define variable owner of the type address */
address owner;

/* This function is executed at initialization and sets the owner of the contract */
function Mortal() { owner = msg.sender; }

/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}

contract Greeter is Mortal {
/* Define variable greeting of the type string */
string greeting;

/* This runs when the contract is executed */
function Greeter(string _greeting) public {
greeting = _greeting;
}

/* Main function */
function greet() constant returns (string) {
return greeting;
}
}

由于Solidity同样具有继承的特性,通过只声明Greeter是Mortal的,Greeter就可以继承”Mortal”合约的所有特征。

这段代码的主函数是greet(),我们之后将调用这个函数来调用智能合约。

编译合约

使用在线编译器remix来编译(需要翻墙):

首先确定右窗格中选择的是Greeter而不是Mortal,然后点击Details来获取编译后的代码,复制WEB3DEPLOY中的代码:

保存到js文件中,命名为contract.js,并修改第一行为:

1
var _greeting = "Hello World!"

部署合约

执行loadScript("contract.js")导入文件,报错如下:

这是因为我们必须要先解锁账户,因为我们需要支付部署合约的GAS费用,解锁后继续执行:

执行eth.getCode(greeter.address)来验证代码是否部署成功:

报错是因为部署合约需要挖矿,启动挖矿后再次执行:

部署成功!

调用合约

执行greeter.greet(),我们可以看到输出:

由于这个调用没引起在区块链上的任何变化,因此它会立即返回并且无需任何Gas费用

使用其他节点调用合约

这首先要求添加节点到私有链中,因此转到ubuntu操作:

上图是在data0进行部署合约的操作

然后,我们要知道通过其他节点调用这个合约需要两个信息:

  1. 合同所在地址
  2. ABI(应用程序二进制接口),这是一种用户手册,描述合同功能的名称以及如何将它们调用到您的JavaScript控制台

获取合同所在地址很简单,greeter.address;即可

要获取ABI,就需要去Remix官网,同样在Detail中获得,复制ABI文本框即可,然后暂时保存到一个abi.txt文件。如果我们想直接把它复制到终端是不行的,因为它含有换行符,因此需要去掉换行符(替换为空格):

1
cat abi.txt | tr '\n' ' '

然后就可以把abi.txt中内容复制到以太坊交互式控制台中,把这段内容赋值给abi,查看赋值后abi的值:

在data1中执行以下命令:

1
myContract = web3.eth.contract(abi).at(address)

address为合约地址

查看myContract以及调用合约:

在节点data1中调用合约好像需要先解锁账户,但是这里我没有解锁也成功了,我想可能是因为这个调用不要消耗Gas值吧

解释交易字段

首先解锁账户0:

1
2
3
4
>  personal.unlockAccount(eth.accounts[0])
Unlock account 0xb01a469b117268418619e61f1b1a6e2fc7e4e7ce
Passphrase:
true

发送交易(从账户0向账户1发送10个以太币):

1
2
3
4
5
> amout = web3.toWei(10, 'ether')
"10000000000000000000"
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amout})
INFO [11-04|16:56:58.227] Setting new local account address=0xB01A469b117268418619E61f1B1a6E2fc7e4E7cE
INFO [11-04|16:56:58.235] Submitted transaction fullhash=0x5eeae4989feab2ef1d3a3c68f3cba9a5a9adafcaef4c4af79ba3088f01df0e76 recipient=0x20949144E5A4590857726BD3B4D95dce04731e78

最后返回的那个字符串即是交易的地址,可以通过getTransaction来查看交易信息,需要注意,此时交易还处于Pending状态(已提交但还未被处理的交易),需要启动挖矿:

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

这样交易就得到处理,使用getTransaction来查看交易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> eth.getTransaction("0x5eeae4989feab2ef1d3a3c68f3cba9a5a9adafcaef4c4af79ba3088f01df0e76")
{
blockHash: "0x897bbbd6b0aeffadb707ad7b37149e93428c1b361809772b6ef99de96713f1e0",
blockNumber: 13,
from: "0xb01a469b117268418619e61f1b1a6e2fc7e4e7ce",
gas: 90000,
gasPrice: 1000000000,
hash: "0x5eeae4989feab2ef1d3a3c68f3cba9a5a9adafcaef4c4af79ba3088f01df0e76",
input: "0x",
nonce: 0,
r: "0x64f0b414949135360554da5165d5115d0df842776d323861121c6dab36e49255",
s: "0x62b9702c7b734a7521f7d1853965c58f54bfe84f0d8273fd52f4381aa8b32524",
to: "0x20949144e5a4590857726bd3b4d95dce04731e78",
transactionIndex: 0,
v: "0x37",
value: 10000000000000000000
}
> eth.accounts
["0xb01a469b117268418619e61f1b1a6e2fc7e4e7ce", "0x20949144e5a4590857726bd3b4d95dce04731e78"]
  • blockHash: 交易所在区块的哈希值。当这个区块处于pending将会返回null。
  • blockNumber:交易所在区块的块号。当这个区块处于pending将会返回null。
  • from: 交易发起者的地址。在这里为accouts[0]。
  • gas:交易发起者提供的gas。
  • gasPrice:交易发起者配置的gas价格,单位是wei。
  • hash:交易的哈希值。
  • input:交易附带的数据。
  • nonce:交易的发起者在之前进行过的交易数量。
  • r:用于产生标识交易发生者的签名(ECDSA签名值)
  • s:用于产生标识交易发生者的签名(ECDSA签名值)
  • to:交易接收者的地址。当这个区块处于pending将会返回null,如果是部署智能合约的交易,这个值也是null。在这里显示为accouts[1]。
  • transactionIndex:交易在区块中的序号。当这个区块处于pending将会返回null。
  • v:用于产生标识交易发生者的签名(ECDSA签名值)
  • value:交易附带的货币量,单位为Wei。在这里即是转账的值

参考链接

如何搭建以太坊私有链

添加私有链节点

部署智能合约

叔块的概念

-------------本文结束感谢您的阅读-------------