关于Ethereum交易是如何从生成并在网络中广播的,如下总结七个步骤:
一、 构建原始交易对象
如下为原始交易对象字段,并对各字段进行展开说明
var rawTx = {
nonce: '0x00',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: '0x0000000000000000000000000000000000000000',
value: '0x00',
data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057'
}
nonce: 记录发起交易的账户已执行交易总数。Nonce的值随着每个新交易的执行不断增加,这能让网络了解执行交易需要遵循的顺序,并且作为交易的重放保护。
gasPrice:该交易每单位gas的价格,Gas价格目前以Gwei为单位(即10^9wei),其范围是大于0.1Gwei,可进行灵活设置。
gasLimit:该交易支付的最高gas上限。该上限能确保在出现交易执行问题(比如陷入无限循环)之时,交易账户不会耗尽所有资金。一旦交易执行完毕,剩余所有gas会返还至交易账户。
to:该交易被送往的地址(调用的合约地址或转账对方的账户地址)。
value:交易发送的以太币总量。
data: 若该交易是以太币交易,则data为空;若是部署合约,则data为合约的bytecode;若是合约调用,则需要从合约ABI中获取函数签名,并取函数签名hash值前4字节与所有参数的编码方式值进行拼接而成,具体参见文章https://github.com/linjie-1/guigulive-operation/wiki/Ethereum%E7%9A%84%E5%90%88%E7%BA%A6ABI%E6%8B%93%E5%B1%95
二、签署交易
需要使用交易账户的私钥对原始交易对象进行签名,下面介绍使用MetaMask以及硬编码两种方式:
MetaMask:私钥只会存储在你的浏览器上,因此你是唯一有权访问你的账户和私钥的人。当你在浏览器上执行交易之时,插件会将你的函数调用转化成原始交易,并用你的私钥签署交易。 Metamask运行Infura运营的节点,并且使用这些节点来广播交易。
硬编码:如下使用了ethereumjs-tx库对交易进行签名,当然硬编码私钥的方式签名交易,不实用,但可以使用一台没有联网的计算机签署该交易。之后,可以复制已签署交易串,并使用联网的计算机将其广播至网络。另一个安全之策是使用 Ledger 或 Trezor 等硬件钱包。这类钱包存储了私钥,而签署交易的私钥已经编程进了硬件本身。它们需要联网的原因只是为了发布你的已签署交易。
var Tx = require('ethereumjs-tx');
var privateKey = "私钥";
var tx = new Tx(rawTx);
tx.sign(privateKey);
var serializedTx = tx.serialize();
web3.eth.sendRawTransaction(serializedTx.toString('hex'), function(err, hash) {
doSomething()
});
三、节点验证
签署过后的交易会提交至以太坊节点。然后节点会验证已签名的交易,确保它是由交易账户签署过的。
四、交易广播
节点验证通过后,会被广播至其对等节点,这些对等节点再将该交易广播给它们的对等节点,以此类推。一旦交易被广播至网络,会输出该交易的id,可以用它来追踪交易的状态。
五、 矿工节点接受交易
矿工节点的职责是将交易打包到区块上。矿工是交易池的维护者,交易先是被添加进交易池,再由矿工进行评估选择来打包。矿工一般将所有交易存储在根据gasPrice价格分类的池中。价格越高,该交易就越有可能被添加进下一个区块。这是矿工节点的常见设定。并且矿池可以容纳的交易数是有限的,gasPrice价格低的交易可能会被放弃。
对于释放阻塞在矿池中交易的方法是提高其gasPrice价格,并维持nonce值和from值不变,nonce和from可以确定标识一笔交易,并且提高的gasPrice必须大于原来价格的10%才可以。这样一来,当矿工接收到新交易时,价格更高的新交易会覆盖之前的交易,使得矿工更容易选择并挖中。
对于如何设置合理的gasPrice,可以参照网站https://ethgasstation.info/
六、矿工节点挖中有效区块并将它广播至网络
矿工将选中的全部交易一起包含进区块。矿工只能选择一定量的交易添加进区块,受限于以太坊设置的单个区块gasLimit,换言之,交易的所有gas总数不能超过区块的gasLimit。
一旦矿工选择将交易包含进区块,这些交易将被验证并包含进一个待处理区块,工作量证明开始。某个矿工节点最终会找到一个有效的区块,并将这一区块添加到区块链上。就像上面介绍的节点广播原始交易一样,矿工节点也会将这一有效区块广播给其他节点。
七、节点接收/同步新区块
最终,全网节点将接收这个新区块,并同步区块链。一旦接收到这个新区块,节点就会执行区块里的所有交易。结合我们使用的truffle调用合约代码, truffle会不断测验连接节点的区块链以求确认。一旦它发现交易被确认,就会执行 then()中的回调逻辑。
参考阅读
https://hudsonjameson.com/2017-06-27-accounts-transactions-gas-ethereum/
http://ethfans.org/posts/life-cycle-of-an-ethereum-transaction
https://medium.com/@jgm.orinoco/releasing-stuck-ethereum-transactions-1390149f297d
https://github.com/linjie-1/guigulive-operation/wiki/Ethereum%E4%BA%A4%E6%98%93%E8%AF%A6%E8%A7%A3
==============================================================
博主看到很多智能合约中有引入:ethereumjs-tx
这么个东西,只知道是和签名相关的,但是在网上却找不到具体的定义。果然最后还得依靠google才知道,原来它是利用私钥进行签名的库。当我们通过web3.js的sendRawTransaction
方法发起交易的时候,必须要用它来签名。
//安装
npm install ethereumjs-tx
//引入
var Tx = require('ethereumjs-tx');
补充:
第一条构建原始交易对象中的data:
其中data,若该交易是以太币交易,则data为空;若是部署合约,则data为合约的bytecode;如果是合约调用或者对函数调用,即是对函数签名以及参数编码的二进制字段。
一个函数调用data的前四个字节数据指定了要调用的函数签名。计算方式是使用函数签名的keccak256(即sha3)的哈希,并取4个字节。写法bytes4(keccak256(“foo()”))需要注意的是,如果有多个参数使用,隔开,要去掉表达式中的所有空格。
示例:
这里大家就能看到,当在调用合约中的函数的时候,必须要取前四个字节。以前看这段代码不太懂这句话是什么意思,现在才算是懂了。
end