以太坊 转账与收款

以太坊区块链互动的唯一方式就是发送交易!无论是简单的转账,还是复杂的智能合约创建/调用,都是包含在交易内发送给以太坊网络的。

如果Geth节点已经停止的话,请用如下命令重启节点,这次我们不需要它有带console控制台互动功能。请读者替换正确的地址为挖矿奖励地址。

cd ether-test
geth --datadir ./db/ --rpc --rpcaddr=127.0.0.1 --rpcport 8545 --rpccorsdomain "*" \
   --rpcapi "eth,net,web3,personal,admin,shh,txpool,debug,miner" \
   --nodiscover --maxpeers 30 --networkid 198989 --port 30303 \
   --mine --minerthreads 1 \
   --etherbase "0x53dc408a8fa060fd3b72b30ca312f4b3f3232f4f"

转账

请确保你的Geth节点尚在运行,我们新开命令行窗口执行attach命令依附到正在运行中的Geth节点:

cd ether-test
geth --datadir ./db attach ipc:./db/geth.ipc

在控制台上我们让节点暂停挖矿。

> miner.stop()
true

同样在控制台上我们解锁的转账方的账户,输入解锁密码,以及解锁时长 300秒 。

> personal.unlockAccount(eth.accounts[0], '123', 300)
true

解锁账户的真正解锁的对象是存储于硬盘的keystore文件。 用户输入密码解锁后将私钥解出,暂存于内存。

在使用签名过后或者解锁时长到达规定后再从内存中移除私钥。这是设计上安全性的考量。不签名直接发送交易,将导致交易异常,无法成功发出。

好,接下来我们发送一笔 10 个以太币的转账,转账接收方是另一个我们持有的账户。

> eth.sendTransaction(
... {
...... from: eth.accounts[0],
...... to: eth.accounts[1],
...... value: web3.toWei(10, 'ether')
....}
..)

"0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed"

交易发送成功!我们取回了一个长长的哈希值:

0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed

这就是交易的哈希值(Transaction Hash, TxHash),这个值能够唯一索引到一笔交易。

查看转账状态

之前我们已经命令挖矿停止。此时我们可以暂停查看全网状态。让我们深入研究此时刚发出去的交易处在什么阶段。

>  txpool.status
{
  pending: 1,
  queued: 0
}

此时交易尚处在 待打包 (pending)的状态,它的具体内容又是什么呢?我们用交易的哈希值来查询一下它。

> eth.getTransaction("0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x53dc408a8fa060fd3b72b30ca312f4b3f3232f4f",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed",
  input: "0x",
  nonce: 1,
  r: "0x9b372d58eb5acce93a8aa742ce3af179576c17208835eaf356c270db24df448c",
  s: "0x5990b41a4f0f8103f4786f785e47f797b83428c96c251ee89bc10cef1d143b35",
  to: "0x6c8f6c9d9f8d63503fc10977db48cd92a15d34ff",
  transactionIndex: 0,
  v: "0x7d9",
  value: 10000000000000000000
}

读者如果回忆 交易的样子 ,就会发现一些非常眼熟的字段名,from/to 字段指明了交易的发送方和接收方; gas/gasPrice 指明了交易方愿意支付的交易费;value 指明了本次转账的以太币金额(wei 为单位);由于我们不是调用合约的交易,所以 input 所代表的数据区域是空的 0x ; nonce 是账户曾经发出过的交易笔数的值;因为尚未被打包入区块,所以 blockHash/blockNumber 都是 0 或者不确定状态。

让矿工运行!让这条交易被打包吧!

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

以上代码将会让挖矿节点重启挖矿,稍等片刻,并在出块1个以后暂停挖矿。停止后区块链再次停止出块,给我们一个机会再观察一下交易池。

> txpool.status
{
  pending: 0,
  queued: 0
}

交易已经成功被矿工捕获并打包,我们再查看一下交易状态。

>eth.getTransaction("0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed")
{
  blockHash: "0xa24f29bab9117a48ef6a83588dfecbb991ff2605dcfffffd3d486edb5a3bfe23",
  blockNumber: 1450,
  from: "0x53dc408a8fa060fd3b72b30ca312f4b3f3232f4f",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed",
  input: "0x",
  nonce: 1,
  r: "0x9b372d58eb5acce93a8aa742ce3af179576c17208835eaf356c270db24df448c",
  s: "0x5990b41a4f0f8103f4786f785e47f797b83428c96c251ee89bc10cef1d143b35",
  to: "0x6c8f6c9d9f8d63503fc10977db48cd92a15d34ff",
  transactionIndex: 0,
  v: "0x7d9",
  value: 10000000000000000000
}

此时交易的 blockHash、blockNumber 都已经填充完毕,我们的交易被包含在了高度 #1450 的区块之中。我们查看一下接收方的余额。

>web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
10

成功转账!至此这笔交易已经完整走完了从发出到最终入块的历程,那么,它被打包入的区块又是怎样的样子呢?我们来查看一下高度为#1450的区块

>eth.getBlock(1450)
{
  difficulty: 230123,
  extraData: "0xd98301080e846765746888676f312e31302e338664617277696e",
  gasLimit: 1041578754,
  gasUsed: 21000,
  hash: "0xa24f29bab9117a48ef6a83588dfecbb991ff2605dcfffffd3d486edb5a3bfe23",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x53dc408a8fa060fd3b72b30ca312f4b3f3232f4f",
  mixHash: "0x9d9a14e1c4a87176f9c6625bf2a5d184c1a667204b0d0f21d638999b1a3da183",
  nonce: "0x4fca8e74a63f357a",
  number: 1450,
  parentHash: "0x7560ceea1bd16c86f383fc31a9185a4014a1703d94838956717a5696dcb9bff4",
  receiptsRoot: "0x61d0ae2203ef95f6e54d9fe14c6c3aa7bcdf9398be15b1f1d164502412863b92",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 656,
  stateRoot: "0x905a8dfd9a0f2ee9f75b50397374fa63bf8119bda52ccb224e2f3bdd97ec4a86",
  timestamp: 1537092501,
  totalDifficulty: 255928573,
  transactions: ["0x45b6be881cf86b79dc7ad8bf4d9cbfafc9aa191062411d6606a086bbc42042ed"],
  transactionsRoot: "0xef95d6a816bb8b56cf47d337553283593a1daafce4d3e0b3c4930080fd59dd55",
  uncles: []
}

可以看到参数 transactions里面包含了一个列表,该列表有且仅有一个刚刚我们发出的交易; 因为没有产生日志事件(非合约调用),所以 logsBloom 日志索引参数被设置为全 0 ; 由于私链网络中有且仅有我们一个节点在挖矿,没有竞争节点,所以 uncles 叔块列表依然为空列表。

参数中尤有意思的是 ExtraData 参数。它的值是一个16进制的值。

0xd98301080e846765746888676f312e31302e338664617277696e

如果我们将其转化为ASCII字符的话,它就是如下一句话,正巧是笔者运行的Geth节点软件的信息。

geth go1.10.3 darwin

至此,我们可以将区块结构的表格拿出来再补充完整:

描述
number 区块高度编号
timestamp Unix 时间戳
hash 本区块的哈希值
parentHash 前任区块的哈希值
nonce PoW 算法的哈希值
extraData 额外的信息
transRoot 交易树的根哈希值
stateRoot 状态树的根哈希值
receiptsRoot 收据树的根哈希值
logsBloom 该块的关键日志索引集合
Miner 挖掘该块的矿工账户地址
gasLimit 当前块允许包容的最大 gas 值
gasUsed 总消耗掉的 gas
difficulty 当前块的挖矿难度值
totDifficulty 区块链的总难度值
mixHash 与 nonce 配合用于挖矿,由前任区块的一部分生成的哈希
size 当前块体积(byte)
sha3Uncles 叔块列表的哈希值
uncles 所引用的叔块列表
transactions 所包含交易列表

下一章:以太坊 部署智能合约

对编程人员来说,以太坊的学习重点在于智能合约的开发。学习以太坊智能合约不仅仅是学习一门新的编程语言 Solidity,更是学习如何使用高效的开发工具、如何进行完整的测试。一个好的智能合约开发者除了通晓语言知识外,还能举 ...