mirror of https://github.com/ethereum/go-ethereum
cmd/evm: make t8ntool handle transaction decoding errors better (#28397)
This change closes https://github.com/ethereum/go-ethereum/issues/27730 . By using an iterator instead of a slice of transactions, we can better handle the case when an individual transaction (within an otherwise well-formed RLP-list) cannot be decoded.pull/28418/head
parent
a8617c6d4d
commit
300df874d7
@ -0,0 +1,194 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/ecdsa" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"os" |
||||
"strings" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/hexutil" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
// txWithKey is a helper-struct, to allow us to use the types.Transaction along with
|
||||
// a `secretKey`-field, for input
|
||||
type txWithKey struct { |
||||
key *ecdsa.PrivateKey |
||||
tx *types.Transaction |
||||
protected bool |
||||
} |
||||
|
||||
func (t *txWithKey) UnmarshalJSON(input []byte) error { |
||||
// Read the metadata, if present
|
||||
type txMetadata struct { |
||||
Key *common.Hash `json:"secretKey"` |
||||
Protected *bool `json:"protected"` |
||||
} |
||||
var data txMetadata |
||||
if err := json.Unmarshal(input, &data); err != nil { |
||||
return err |
||||
} |
||||
if data.Key != nil { |
||||
k := data.Key.Hex()[2:] |
||||
if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { |
||||
return err |
||||
} else { |
||||
t.key = ecdsaKey |
||||
} |
||||
} |
||||
if data.Protected != nil { |
||||
t.protected = *data.Protected |
||||
} else { |
||||
t.protected = true |
||||
} |
||||
// Now, read the transaction itself
|
||||
var tx types.Transaction |
||||
if err := json.Unmarshal(input, &tx); err != nil { |
||||
return err |
||||
} |
||||
t.tx = &tx |
||||
return nil |
||||
} |
||||
|
||||
// signUnsignedTransactions converts the input txs to canonical transactions.
|
||||
//
|
||||
// The transactions can have two forms, either
|
||||
// 1. unsigned or
|
||||
// 2. signed
|
||||
//
|
||||
// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set.
|
||||
// If so, we sign it here and now, with the given `secretKey`
|
||||
// If the condition above is not met, then it's considered a signed transaction.
|
||||
//
|
||||
// To manage this, we read the transactions twice, first trying to read the secretKeys,
|
||||
// and secondly to read them with the standard tx json format
|
||||
func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { |
||||
var signedTxs []*types.Transaction |
||||
for i, tx := range txs { |
||||
var ( |
||||
v, r, s = tx.tx.RawSignatureValues() |
||||
signed *types.Transaction |
||||
err error |
||||
) |
||||
if tx.key == nil || v.BitLen()+r.BitLen()+s.BitLen() != 0 { |
||||
// Already signed
|
||||
signedTxs = append(signedTxs, tx.tx) |
||||
continue |
||||
} |
||||
// This transaction needs to be signed
|
||||
if tx.protected { |
||||
signed, err = types.SignTx(tx.tx, signer, tx.key) |
||||
} else { |
||||
signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key) |
||||
} |
||||
if err != nil { |
||||
return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err)) |
||||
} |
||||
signedTxs = append(signedTxs, signed) |
||||
} |
||||
return signedTxs, nil |
||||
} |
||||
|
||||
func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (txIterator, error) { |
||||
var txsWithKeys []*txWithKey |
||||
if txStr != stdinSelector { |
||||
data, err := os.ReadFile(txStr) |
||||
if err != nil { |
||||
return nil, NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) |
||||
} |
||||
if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list
|
||||
var body hexutil.Bytes |
||||
if err := json.Unmarshal(data, &body); err != nil { |
||||
return nil, err |
||||
} |
||||
return newRlpTxIterator(body), nil |
||||
} |
||||
if err := json.Unmarshal(data, &txsWithKeys); err != nil { |
||||
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) |
||||
} |
||||
} else { |
||||
if len(inputData.TxRlp) > 0 { |
||||
// Decode the body of already signed transactions
|
||||
return newRlpTxIterator(common.FromHex(inputData.TxRlp)), nil |
||||
} |
||||
// JSON encoded transactions
|
||||
txsWithKeys = inputData.Txs |
||||
} |
||||
// We may have to sign the transactions.
|
||||
signer := types.LatestSignerForChainID(chainConfig.ChainID) |
||||
txs, err := signUnsignedTransactions(txsWithKeys, signer) |
||||
return newSliceTxIterator(txs), err |
||||
} |
||||
|
||||
type txIterator interface { |
||||
// Next returns true until EOF
|
||||
Next() bool |
||||
// Tx returns the next transaction, OR an error.
|
||||
Tx() (*types.Transaction, error) |
||||
} |
||||
|
||||
type sliceTxIterator struct { |
||||
idx int |
||||
txs []*types.Transaction |
||||
} |
||||
|
||||
func newSliceTxIterator(transactions types.Transactions) txIterator { |
||||
return &sliceTxIterator{0, transactions} |
||||
} |
||||
|
||||
func (ait *sliceTxIterator) Next() bool { |
||||
return ait.idx < len(ait.txs) |
||||
} |
||||
|
||||
func (ait *sliceTxIterator) Tx() (*types.Transaction, error) { |
||||
if ait.idx < len(ait.txs) { |
||||
ait.idx++ |
||||
return ait.txs[ait.idx-1], nil |
||||
} |
||||
return nil, io.EOF |
||||
} |
||||
|
||||
type rlpTxIterator struct { |
||||
in *rlp.Stream |
||||
} |
||||
|
||||
func newRlpTxIterator(rlpData []byte) txIterator { |
||||
in := rlp.NewStream(bytes.NewBuffer(rlpData), 1024*1024) |
||||
in.List() |
||||
return &rlpTxIterator{in} |
||||
} |
||||
|
||||
func (it *rlpTxIterator) Next() bool { |
||||
return it.in.MoreDataInList() |
||||
} |
||||
|
||||
func (it *rlpTxIterator) Tx() (*types.Transaction, error) { |
||||
var a types.Transaction |
||||
if err := it.in.Decode(&a); err != nil { |
||||
return nil, err |
||||
} |
||||
return &a, nil |
||||
} |
@ -0,0 +1,77 @@ |
||||
This example comes from https://github.com/ethereum/go-ethereum/issues/27730. |
||||
The input transactions contain three transactions, number `0` and `2` are taken from |
||||
`testdata/13`, whereas number `1` is taken from #27730. |
||||
|
||||
The problematic second transaction cannot be RLP-decoded, and the expectation is |
||||
that that particular transaction should be rejected, but number `0` and `1` should |
||||
still be accepted. |
||||
|
||||
``` |
||||
$ go run . t8n --input.alloc=./testdata/30/alloc.json --input.txs=./testdata/30/txs_more.rlp --input.env=./testdata/30/env.json --output.result=stdout --output.alloc=stdout --state.fork=Cancun |
||||
WARN [10-22|15:38:03.283] rejected tx index=1 error="rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" |
||||
INFO [10-22|15:38:03.284] Trie dumping started root=348312..915c93 |
||||
INFO [10-22|15:38:03.284] Trie dumping complete accounts=3 elapsed="160.831µs" |
||||
{ |
||||
"alloc": { |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { |
||||
"code": "0x60004960005500", |
||||
"balance": "0xde0b6b3a7640000" |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0xde0b6b3a7640000" |
||||
}, |
||||
"0xd02d72e067e77158444ef2020ff2d325f929b363": { |
||||
"balance": "0xfffffffb8390", |
||||
"nonce": "0x3" |
||||
} |
||||
}, |
||||
"result": { |
||||
"stateRoot": "0x3483124b6710486c9fb3e07975669c66924697c88cccdcc166af5e1218915c93", |
||||
"txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", |
||||
"receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", |
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"receipts": [ |
||||
{ |
||||
"type": "0x2", |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0x5208", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"effectiveGasPrice": null, |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x0" |
||||
}, |
||||
{ |
||||
"type": "0x2", |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0xa410", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"effectiveGasPrice": null, |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x1" |
||||
} |
||||
], |
||||
"rejected": [ |
||||
{ |
||||
"index": 1, |
||||
"error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" |
||||
} |
||||
], |
||||
"currentDifficulty": null, |
||||
"gasUsed": "0xa410", |
||||
"currentBaseFee": "0x7", |
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" |
||||
} |
||||
} |
||||
|
||||
``` |
@ -0,0 +1,23 @@ |
||||
{ |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x60004960005500", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
}, |
||||
"0xd02d72e067e77158444ef2020ff2d325f929b363" : { |
||||
"balance": "0x01000000000000", |
||||
"code": "0x", |
||||
"nonce": "0x01", |
||||
"storage": { |
||||
} |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
{ |
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||
"currentNumber" : "0x01", |
||||
"currentTimestamp" : "0x03e8", |
||||
"currentGasLimit" : "0x1000000000", |
||||
"previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da", |
||||
"currentDataGasUsed" : "0x2000", |
||||
"parentTimestamp" : "0x00", |
||||
"parentDifficulty" : "0x00", |
||||
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000", |
||||
"withdrawals" : [ |
||||
], |
||||
"parentBaseFee" : "0x08", |
||||
"parentGasUsed" : "0x00", |
||||
"parentGasLimit" : "0x1000000000", |
||||
"parentExcessBlobGas" : "0x1000", |
||||
"parentBlobGasUsed" : "0x2000", |
||||
"blockHashes" : { |
||||
"0" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da" |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
{ |
||||
"alloc": { |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { |
||||
"code": "0x60004960005500", |
||||
"balance": "0xde0b6b3a7640000" |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0xde0b6b3a7640000" |
||||
}, |
||||
"0xd02d72e067e77158444ef2020ff2d325f929b363": { |
||||
"balance": "0xfffffffb8390", |
||||
"nonce": "0x3" |
||||
} |
||||
}, |
||||
"result": { |
||||
"stateRoot": "0x3483124b6710486c9fb3e07975669c66924697c88cccdcc166af5e1218915c93", |
||||
"txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", |
||||
"receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", |
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"receipts": [ |
||||
{ |
||||
"type": "0x2", |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0x5208", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"effectiveGasPrice": null, |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x0" |
||||
}, |
||||
{ |
||||
"type": "0x2", |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0xa410", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"effectiveGasPrice": null, |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x1" |
||||
} |
||||
], |
||||
"rejected": [ |
||||
{ |
||||
"index": 1, |
||||
"error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" |
||||
} |
||||
], |
||||
"currentDifficulty": null, |
||||
"gasUsed": "0xa410", |
||||
"currentBaseFee": "0x7", |
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"currentExcessBlobGas": "0x0", |
||||
"blobGasUsed": "0x0" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
"0xf8dbb8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67" |
@ -0,0 +1 @@ |
||||
"0xf901adb86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" |
Loading…
Reference in new issue