Skip to content

Commit 7a72868

Browse files
holimancolinlyguo
authored andcommitted
cmd/evm: make t8ntool handle transaction decoding errors better (ethereum#28397)
This change closes ethereum#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.
1 parent 9f83e9d commit 7a72868

File tree

10 files changed

+420
-15
lines changed

10 files changed

+420
-15
lines changed

cmd/evm/internal/t8ntool/execution.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ type rejectedTx struct {
117117

118118
// Apply applies a set of transactions to a pre-state
119119
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
120-
txs types.Transactions, miningReward int64,
121-
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) {
120+
txIt txIterator, miningReward int64,
121+
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, []byte, error) {
122122
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
123123
// required blockhashes
124124
var hashError error
@@ -195,25 +195,39 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
195195
core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb)
196196
}
197197
var blobGasUsed uint64
198-
for i, tx := range txs {
198+
199+
for i := 0; txIt.Next(); i++ {
200+
tx, err := txIt.Tx()
201+
if err != nil {
202+
log.Warn("rejected tx", "index", i, "error", err)
203+
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
204+
continue
205+
}
199206
if tx.Type() == types.BlobTxType && vmContext.BlobBaseFee == nil {
200207
errMsg := "blob tx used but field env.ExcessBlobGas missing"
201208
log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", errMsg)
202209
rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg})
203210
continue
204211
}
205-
if tx.Type() == types.BlobTxType {
206-
blobGasUsed += uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes()))
207-
}
208212
msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee)
209213
if err != nil {
210214
log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err)
211215
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
212216
continue
213217
}
218+
if tx.Type() == types.BlobTxType {
219+
txBlobGas := uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes()))
220+
if used, max := blobGasUsed+txBlobGas, uint64(params.MaxBlobGasPerBlock); used > max {
221+
err := fmt.Errorf("blob gas (%d) would exceed maximum allowance %d", used, max)
222+
log.Warn("rejected tx", "index", i, "err", err)
223+
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
224+
continue
225+
}
226+
blobGasUsed += txBlobGas
227+
}
214228
tracer, err := getTracerFn(txIndex, tx.Hash())
215229
if err != nil {
216-
return nil, nil, err
230+
return nil, nil, nil, err
217231
}
218232
vmConfig.Tracer = tracer
219233
statedb.SetTxContext(tx.Hash(), txIndex)
@@ -244,7 +258,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
244258
}
245259
includedTxs = append(includedTxs, tx)
246260
if hashError != nil {
247-
return nil, nil, NewError(ErrorMissingBlockhash, hashError)
261+
return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError)
248262
}
249263
gasUsed += msgResult.UsedGas
250264

@@ -319,7 +333,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
319333
// Commit block
320334
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
321335
if err != nil {
322-
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
336+
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
323337
}
324338
execRs := &ExecutionResult{
325339
StateRoot: root,
@@ -345,9 +359,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
345359
// for accessing latest states.
346360
statedb, err = state.New(root, statedb.Database(), nil)
347361
if err != nil {
348-
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err))
362+
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err))
349363
}
350-
return statedb, execRs, nil
364+
body, _ := rlp.EncodeToBytes(includedTxs)
365+
return statedb, execRs, body, nil
351366
}
352367

353368
func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB {

cmd/evm/internal/t8ntool/transition.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func Transition(ctx *cli.Context) error {
149149
// Check if anything needs to be read from stdin
150150
var (
151151
prestate Prestate
152-
txs types.Transactions // txs to apply
152+
txIt txIterator // txs to apply
153153
allocStr = ctx.String(InputAllocFlag.Name)
154154

155155
envStr = ctx.String(InputEnvFlag.Name)
@@ -194,7 +194,7 @@ func Transition(ctx *cli.Context) error {
194194
// Set the chain id
195195
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
196196

197-
if txs, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil {
197+
if txIt, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil {
198198
return err
199199
}
200200
if err := applyCurieChecks(&prestate.Env, chainConfig, inputData.ParentL1BaseFee); err != nil {
@@ -210,11 +210,10 @@ func Transition(ctx *cli.Context) error {
210210
return err
211211
}
212212
// Run the test and aggregate the result
213-
s, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer)
213+
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer)
214214
if err != nil {
215215
return err
216216
}
217-
body, _ := rlp.EncodeToBytes(txs)
218217
// Dump the excution result
219218
collector := make(Alloc)
220219
s.DumpToCollector(collector, nil)
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2023 The go-ethereum Authors
2+
// This file is part of go-ethereum.
3+
//
4+
// go-ethereum is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// go-ethereum is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package t8ntool
18+
19+
import (
20+
"bytes"
21+
"crypto/ecdsa"
22+
"encoding/json"
23+
"fmt"
24+
"io"
25+
"os"
26+
"strings"
27+
28+
"github.com/ethereum/go-ethereum/common"
29+
"github.com/ethereum/go-ethereum/common/hexutil"
30+
"github.com/ethereum/go-ethereum/core/types"
31+
"github.com/ethereum/go-ethereum/crypto"
32+
"github.com/ethereum/go-ethereum/params"
33+
"github.com/ethereum/go-ethereum/rlp"
34+
)
35+
36+
// txWithKey is a helper-struct, to allow us to use the types.Transaction along with
37+
// a `secretKey`-field, for input
38+
type txWithKey struct {
39+
key *ecdsa.PrivateKey
40+
tx *types.Transaction
41+
protected bool
42+
}
43+
44+
func (t *txWithKey) UnmarshalJSON(input []byte) error {
45+
// Read the metadata, if present
46+
type txMetadata struct {
47+
Key *common.Hash `json:"secretKey"`
48+
Protected *bool `json:"protected"`
49+
}
50+
var data txMetadata
51+
if err := json.Unmarshal(input, &data); err != nil {
52+
return err
53+
}
54+
if data.Key != nil {
55+
k := data.Key.Hex()[2:]
56+
if ecdsaKey, err := crypto.HexToECDSA(k); err != nil {
57+
return err
58+
} else {
59+
t.key = ecdsaKey
60+
}
61+
}
62+
if data.Protected != nil {
63+
t.protected = *data.Protected
64+
} else {
65+
t.protected = true
66+
}
67+
// Now, read the transaction itself
68+
var tx types.Transaction
69+
if err := json.Unmarshal(input, &tx); err != nil {
70+
return err
71+
}
72+
t.tx = &tx
73+
return nil
74+
}
75+
76+
// signUnsignedTransactions converts the input txs to canonical transactions.
77+
//
78+
// The transactions can have two forms, either
79+
// 1. unsigned or
80+
// 2. signed
81+
//
82+
// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set.
83+
// If so, we sign it here and now, with the given `secretKey`
84+
// If the condition above is not met, then it's considered a signed transaction.
85+
//
86+
// To manage this, we read the transactions twice, first trying to read the secretKeys,
87+
// and secondly to read them with the standard tx json format
88+
func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) {
89+
var signedTxs []*types.Transaction
90+
for i, tx := range txs {
91+
var (
92+
v, r, s = tx.tx.RawSignatureValues()
93+
signed *types.Transaction
94+
err error
95+
)
96+
if tx.key == nil || v.BitLen()+r.BitLen()+s.BitLen() != 0 {
97+
// Already signed
98+
signedTxs = append(signedTxs, tx.tx)
99+
continue
100+
}
101+
// This transaction needs to be signed
102+
if tx.protected {
103+
signed, err = types.SignTx(tx.tx, signer, tx.key)
104+
} else {
105+
signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key)
106+
}
107+
if err != nil {
108+
return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err))
109+
}
110+
signedTxs = append(signedTxs, signed)
111+
}
112+
return signedTxs, nil
113+
}
114+
115+
func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (txIterator, error) {
116+
var txsWithKeys []*txWithKey
117+
if txStr != stdinSelector {
118+
data, err := os.ReadFile(txStr)
119+
if err != nil {
120+
return nil, NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err))
121+
}
122+
if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list
123+
var body hexutil.Bytes
124+
if err := json.Unmarshal(data, &body); err != nil {
125+
return nil, err
126+
}
127+
return newRlpTxIterator(body), nil
128+
}
129+
if err := json.Unmarshal(data, &txsWithKeys); err != nil {
130+
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err))
131+
}
132+
} else {
133+
if len(inputData.TxRlp) > 0 {
134+
// Decode the body of already signed transactions
135+
return newRlpTxIterator(common.FromHex(inputData.TxRlp)), nil
136+
}
137+
// JSON encoded transactions
138+
txsWithKeys = inputData.Txs
139+
}
140+
// We may have to sign the transactions.
141+
signer := types.LatestSignerForChainID(chainConfig.ChainID)
142+
txs, err := signUnsignedTransactions(txsWithKeys, signer)
143+
return newSliceTxIterator(txs), err
144+
}
145+
146+
type txIterator interface {
147+
// Next returns true until EOF
148+
Next() bool
149+
// Tx returns the next transaction, OR an error.
150+
Tx() (*types.Transaction, error)
151+
}
152+
153+
type sliceTxIterator struct {
154+
idx int
155+
txs []*types.Transaction
156+
}
157+
158+
func newSliceTxIterator(transactions types.Transactions) txIterator {
159+
return &sliceTxIterator{0, transactions}
160+
}
161+
162+
func (ait *sliceTxIterator) Next() bool {
163+
return ait.idx < len(ait.txs)
164+
}
165+
166+
func (ait *sliceTxIterator) Tx() (*types.Transaction, error) {
167+
if ait.idx < len(ait.txs) {
168+
ait.idx++
169+
return ait.txs[ait.idx-1], nil
170+
}
171+
return nil, io.EOF
172+
}
173+
174+
type rlpTxIterator struct {
175+
in *rlp.Stream
176+
}
177+
178+
func newRlpTxIterator(rlpData []byte) txIterator {
179+
in := rlp.NewStream(bytes.NewBuffer(rlpData), 1024*1024)
180+
in.List()
181+
return &rlpTxIterator{in}
182+
}
183+
184+
func (it *rlpTxIterator) Next() bool {
185+
return it.in.MoreDataInList()
186+
}
187+
188+
func (it *rlpTxIterator) Tx() (*types.Transaction, error) {
189+
var a types.Transaction
190+
if err := it.in.Decode(&a); err != nil {
191+
return nil, err
192+
}
193+
return &a, nil
194+
}

cmd/evm/t8n_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,14 @@ func TestT8n(t *testing.T) {
275275
output: t8nOutput{alloc: true, result: true},
276276
expOut: "exp.json",
277277
},
278+
{ // More cancun test, plus example of rlp-transaction that cannot be decoded properly
279+
base: "./testdata/30",
280+
input: t8nInput{
281+
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
282+
},
283+
output: t8nOutput{alloc: true, result: true},
284+
expOut: "exp.json",
285+
},
278286
} {
279287
args := []string{"t8n"}
280288
args = append(args, tc.output.get()...)

0 commit comments

Comments
 (0)