今天把前段在实现ETH交易过程中遇到的问题做一下记录,算是对这部分知识的复习。要想在以太坊中完成一笔交易,可以借助节点对外开放的eth_sendTransaction
或eth_sendRawTransaction
这两个JsonRpc接口。我们只需要拼接好这个接口需要的参数再通过网络提交到节点即可。本文的重点是叙述怎么来拼接这个目标参数,由于在钱包开发中主要使用eth_sendRawTransaction
这个接口,下面就对这接口所需要的参数进行阐述。
交易构造
通过查阅Ethereum的文档,知道构造原始交易所需要的结构如下所示:
1 | /// Description of a Transaction, pending or in the chain. |
注意:自己在实现过程中定义原始交易参数结构体时,必须要确保参数顺序相同。在构造交易时会将该结构体使用RLP方式编码,当节点收到原始交易时会按照这个结构对收到的数据做编码还原;
从RawTransaction
我们知道在一笔交易中涉及到nonce
,to
,value
,gas_price
,gas,data
这五个属性。这下面是对这些属性的说明:
nonce
在区块链交易中都需要有nonce这个参数,用于表示当前发送地址的第几笔交易,能够确保交易的顺序,防止节点被交易重放攻击。注意:
- 初始交易是从编号0开始的,可以通过调用
eth_getTransactionCount
获取当前交易的值; - 节点在验证交易时会校验当前的nonce是否为正确的值,若交易中的nonce值比真实值小,则这笔交易会直接返回nonce错误;
- 若交易中的nonce值比真实值大,则会将该交易放入一个
future
队列里面,等待中间缺失的交易进来后再继续验证这笔交易;地址(to)
to
表示交易的接受地址,这个地址通常可以是一个外部地址(该地址对应的私钥有实际的控制者),表示一笔转账操作;也可以是一个内部地址(合约地址),表示合约调用。value
这个值表示该交易需要转给目标地址的ETH
数量,需要注意填写的数值是最小单位(wei),通常在钱包界面上填写的数值单位是ether
,在处理的时候需要做单位转换;这里有几种情况需要注意: - 填写正常的数值且to 地址是一个外部地址,表示将指定数量的token转到指定地址;
- 填写的数值为0,且to地址是一个外部地址,这种操作除了浪费gas之外,没有任何作用;
- 当to地址是一个合约地址时且value不为空,这样的交易也会将发送者地址上的eth代币转到合约地址上,由于合约地址是内部地址,没人知道对应的私钥,所以这些token会成为死币;
gas_price
表示每个单位的gas值多少wei,我们知道Ethereum EVM在执行过程中每个操作都需要消耗gas,gas是一个不随ETH波动而波动的单位定义
,用于表示调用者愿意为这笔交易每个步骤花费多少token,通常情况下调用者给出的gas_price
价格越高,矿工会优先执行执行;为了防止恶意抬高gas_price,Ethereum针对gas_price
有专门的算法来进行控制,开发者在构造交易的时候,直接调用节点提供的rpc接口eth_gasPrice
即可;还需要注意的是,这里的数值也需要填写最小的单位wei。gas
表示愿意交易在执行过程中,愿意支付的最大gas数量,也就是我们常见的gas_limit数值;若是指定的gas数量太少,交易在执行的过程中gas消耗完后,就会返回错误;若是指定的gas数量比较大,调用者账户上也有足够的gas可扣,则交易会正常的执行,在执行完后会将多余的gas返回到调用者的账户上。data
在以太坊中针对每笔交易都可以填写附加信息,针对这个字段的内容我打算放到下一篇关于合约调用的文章中。
以上部分就是构造一个以太坊交易所需要的基本数据,要是构造的交易能够在链上被虚拟机执行,还需要对交易进行签名操作;编码
在以太坊中,数据的编码使用RLP
的编码方式来进行的,关于编码实现的具体方式,可以查看wiki,只是需要注意在编码的过程中,需要注意字段的顺序
1 | //对RawTransaction 数据使用RLP编码 |
之所以按照fn encode
方式将数据添加到RlpStream
实例中,是因为节点验证交易时会进行如下方式的数据解码
1 | //还原出原RLP数据格式 |
签名生成
以太坊使用Secp256k1算法来签名,我们知道签名都有计算源数据的hash值过程。针对交易hash值的计算过程如下:
1 | fn hash(&self, chain_id: Option<u64>) -> [u8; 32] { |
其中chain_id
表示当前交易用于的目标链,以太坊使用这个参数的目的是防止在不同的链上提交相同的数据,让节点不能提前检查出风险交易,增加了链的稳定性。可以通过构造JsonRpc
请求数据{"method":"eth_chainId","params":[],"id":1,"jsonrpc":"2.0"}
获取到;
hash数据签名的过程如下:
1 | fn ecdsa_sign(hash: &[u8], private_key: &[u8], chain_id: u64) -> EcdsaSig { |
签名最后得到的v
、r
、s
值和交易结构体编码的数据一起构成了eth_sendRawTransaction
这个接口所需要的数据;下面是完整的交易签名实现:
1 | pub fn sign(&self, private_key: &[u8], chain_id: Option<u64>) -> Vec<u8> { |
从上述代码可以看出,我们通常听到的交易签名,其实是包含了通过keccak256
计算交易hash值、使用Sec256K1
对Hash签名并将签名结果和交易详情编码在一起,最后输出Hex
格式数据的过程。
总结
通过上面的描述详细说明了以太坊交易构成的过程,这也是当前钱包在构造用户交易的核心过程,该过程涉及到的内容很多,后续可以继续对里面的知识点进行阐述。