为何签名交易需编码为字节后发送?Go以太坊三种方案解析
我希望通过合约调用向以太坊区块链写入数据,找到了三种实现方案。方案2和方案3相比方案1多了额外的字节编码操作,但我无法理解该操作的必要性。已知signedTx与txToSend均为*types.Transaction类型,go-ethereum文档仅说明SendTransaction用于将签名交易注入待执行池,未提供更多细节,因此疑问:为何方案2和方案3需要这些额外的字节处理步骤?
三种方案代码如下:
方案1:最简未处理方案
这是未对signedTx进行任何处理的最简方案。
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) if err != nil { log.Fatal(err) } txErr := client.SendTransaction(context.Background(), tx) if txErr != nil { log.Fatalf("Error calling contract: %v", err) }
方案2:基于Rlp编码的原始交易处理(来自《Go以太坊书籍》)
该实现来自《Go以太坊书籍》的创建原始交易和发送原始交易章节。
tx_signed := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) signedTx, err := types.SignTx(tx_signed, types.NewEIP155Signer(chainID), privateKey) if err != nil { log.Fatal(err) } ts := types.Transactions{signedTx} rawTxBytes := ts.GetRlp(0) rawTxHex := hex.EncodeToString(rawTxBytes) rawBytes, err := hex.DecodeString(rawTxHex) tx := new(types.Transaction) rlp.DecodeBytes(rawBytes, &tx) txErr := client.SendTransaction(context.Background(), tx) if txErr != nil { log.Fatalf("Error calling contract: %v", err) }
方案3:使用EncodeIndex的更新版字节处理
该实现与方案2基本一致,但使用更新的EncodeIndex(i int, w *bytes.Buffer)函数进行字节处理。
tx_signed := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) signedTx, err := types.SignTx(tx_signed, types.NewEIP155Signer(chainID), privateKey) if err != nil { log.Fatal(err) } ts := types.Transactions{signedTx} b := new(bytes.Buffer) ts.EncodeIndex(0, b) rawTxBytes := b.Bytes() txToSend := new(types.Transaction) rlp.DecodeBytes(rawTxBytes, &txToSend) txErr := client.SendTransaction(context.Background(), tx) if txErr != nil { log.Fatalf("Error calling contract: %v", err) }
首先明确:方案2和3里的额外字节处理步骤,在直接调用SendTransaction的场景下完全是冗余的,但这些步骤存在的原因主要和以下几个场景、历史因素有关:
原始交易的传输与存储需求
以太坊网络中交易最终是以RLP编码的原始字节形式在节点间传输、存储在区块链上的。如果需要把签名后的交易导出为十六进制字符串(比如离线签名后通过其他渠道提交、或者保存交易备份),就需要先做RLP编码转十六进制的操作。方案2里的hex.EncodeToString(rawTxBytes)就是为了得到可传输的原始交易十六进制串,后续的解码只是为了再转回types.Transaction对象用于发送——但如果只是本地直接发送,这一步完全没必要。验证签名交易的正确性
将签名后的交易做RLP编码再解码的过程,相当于一次序列化/反序列化的校验。可以验证签名后的交易是否符合RLP编码规范,确保交易数据在签名后没有被意外篡改(虽然types.SignTx本身已经保证了正确性,但某些复杂场景下开发者可能需要额外验证)。历史API兼容性
早期的go-ethereum版本中,部分节点API(比如eth_sendRawTransaction)只接受原始十六进制交易数据,而不是types.Transaction对象。《Go以太坊书籍》里的方案可能是为了演示如何生成原始交易串,适配这类API的调用场景。而现在的SendTransaction方法已经可以直接接受签名后的types.Transaction,所以这些步骤就显得多余了。方案3的
EncodeIndex是GetRlp的替代types.Transactions.GetRlp是旧版API,EncodeIndex是go-ethereum更新后的替代方法,作用都是获取单个交易的RLP编码字节,本质和方案2的核心操作一致,只是API更规范。
总结:如果你的需求只是本地签名后直接调用SendTransaction发送交易,方案1是最优选择,方案2和3的额外步骤完全没必要。这些步骤只在需要处理原始交易字节/十六进制串的场景下才有意义。
内容的提问来源于stack exchange,提问作者rihekopo

