diff --git a/core/taiko_state_processor.go b/core/taiko_state_processor.go new file mode 100644 index 000000000000..b8ddd177948e --- /dev/null +++ b/core/taiko_state_processor.go @@ -0,0 +1,55 @@ +package core + +import ( + "context" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +// ApplyTransactionWithTimeout applies a transaction to the state with a timeout context. +// If the context is cancelled or times out, the EVM execution will be stopped. +// +// Parameters: +// - ctx: The context to control the timeout and cancellation. +// - hashFunc: Function to retrieve block hashes. +// - config: The chain configuration parameters. +// - bc: The blockchain context. +// - author: The address of the block author. +// - gp: The gas pool for the transaction. +// - statedb: The state database. +// - header: The block header. +// - tx: The transaction to be applied. +// - usedGas: Pointer to the used gas value. +// - cfg: The EVM configuration. +// +// Returns: +// - *types.Receipt: The receipt of the transaction. +// - error: An error if the transaction application fails. +func ApplyTransactionWithTimeout(ctx context.Context, hashFuncWrapper func(vm.GetHashFunc) vm.GetHashFunc, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { + msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) + if err != nil { + return nil, err + } + // CHANGE(taiko): decode the basefeeSharingPctg config from the extradata, and + // add it to the Message, if its an ontake block. + if config.IsOntake(header.Number) { + msg.BasefeeSharingPctg = DecodeOntakeExtraData(header.Extra) + } + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author) + txContext := NewEVMTxContext(msg) + vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) + go func() { + <-ctx.Done() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + // Stop evm execution. Note cancellation is not necessarily immediate. + vmenv.Cancel() + } + }() + return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) +} diff --git a/eth/tracers/taiko_api.go b/eth/tracers/taiko_api.go index 3df985744b6a..88ffb6a9844b 100644 --- a/eth/tracers/taiko_api.go +++ b/eth/tracers/taiko_api.go @@ -20,7 +20,9 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -var noopTracer = "noopTracer" +type TaikoBackend interface { + BlockChain() *core.BlockChain +} // provingPreflightResult is the result of a proving preflight request. type provingPreflightResult struct { @@ -37,7 +39,6 @@ type provingPreflightTask struct { parent *types.Block // Parent block of the block to preflight block *types.Block // Block to preflight the transactions from release StateReleaseFunc // The function to release the held resource for this task - results []*txTraceResult // Trace results produced by the task preflight *provingPreflightResult // Preflight results produced by the task } @@ -125,11 +126,6 @@ func (api *API) provingPreflights(start, end *types.Block, config *TraceConfig, if threads > blocks { threads = blocks } - // no need any execution traces when preflighting - if config == nil { - config = &TraceConfig{} - } - config.Tracer = &noopTracer var ( pend = new(sync.WaitGroup) ctx = context.Background() @@ -144,38 +140,22 @@ func (api *API) provingPreflights(start, end *types.Block, config *TraceConfig, // Fetch and execute the block trace taskCh for task := range taskCh { - var ( - signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) - blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) - ) - - newHashFunc := newHashFuncWithRecord(blockCtx.GetHash) - blockCtx.GetHash = newHashFunc.getHash + newHashFunc := newHashFuncWithRecord() // Trace all the transactions contained within for i, tx := range task.block.Transactions() { if i == 0 && api.backend.ChainConfig().Taiko { if err := tx.MarkAsAnchor(); err != nil { log.Warn("Mark anchor transaction error", "error", err) - task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} task.preflight.Error = err break } } - msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee()) - txctx := &Context{ - BlockHash: task.block.Hash(), - BlockNumber: task.block.Number(), - TxIndex: i, - TxHash: tx.Hash(), - } - res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, task.statedb, config) + err := api.applyTx(ctx, newHashFunc.hashFuncWrapper, i, tx, task.block.Header(), task.statedb, config) if err != nil { - task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) task.preflight.Error = err break } - task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} } // Tracing state is used up, queue it for de-referencing. Note the // state is the parent state of trace block, use block.number-1 as @@ -306,7 +286,6 @@ func (api *API) provingPreflights(start, end *types.Block, config *TraceConfig, parent: block, block: next, release: release, - results: make([]*txTraceResult, len(txs)), preflight: &provingPreflightResult{ Block: next, InitAccountProofs: []*ethapi.AccountResult{}, @@ -350,6 +329,32 @@ func (api *API) provingPreflights(start, end *types.Block, config *TraceConfig, return retCh } +// applyTx configures a new tracer according to the provided configuration, and +// executes the given message in the provided environment. The return value will +// be tracer dependent. +func (api *API) applyTx(ctx context.Context, hashFuncWrapper func(vm.GetHashFunc) vm.GetHashFunc, txIdx int, tx *types.Transaction, header *types.Header, statedb *state.StateDB, config *TraceConfig) error { + var ( + err error + timeout = defaultTraceTimeout + usedGas uint64 + ) + if config != nil && config.Timeout != nil { + if timeout, err = time.ParseDuration(*config.Timeout); err != nil { + return err + } + } + + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + statedb.SetTxContext(tx.Hash(), txIdx) + _, err = core.ApplyTransactionWithTimeout(deadlineCtx, hashFuncWrapper, api.backend.ChainConfig(), api.backend.(TaikoBackend).BlockChain(), nil, new(core.GasPool).AddGas(tx.Gas()), statedb, header, tx, &usedGas, vm.Config{}) + if err != nil { + return err + } + return nil +} + // getProof returns the Merkle-proof for a given account and optionally some storage keys. func (api *API) getProof(ctx context.Context, address common.Address, storageKeys []common.Hash, parent *types.Block, reexec uint64) (*ethapi.AccountResult, []byte, error) { var ( @@ -435,13 +440,17 @@ type hashFuncWithRecord struct { hashFunc vm.GetHashFunc } -func newHashFuncWithRecord(hashFunc vm.GetHashFunc) *hashFuncWithRecord { +func newHashFuncWithRecord() *hashFuncWithRecord { return &hashFuncWithRecord{ - hashes: make(map[uint64]common.Hash), - hashFunc: hashFunc, + hashes: make(map[uint64]common.Hash), } } +func (r *hashFuncWithRecord) hashFuncWrapper(hashFunc vm.GetHashFunc) vm.GetHashFunc { + r.hashFunc = hashFunc + return r.getHash +} + func (r *hashFuncWithRecord) getHash(n uint64) common.Hash { hash := r.hashFunc(n) r.hashes[n] = hash diff --git a/eth/tracers/taiko_api_test.go b/eth/tracers/taiko_api_test.go new file mode 100644 index 000000000000..b1518b08656e --- /dev/null +++ b/eth/tracers/taiko_api_test.go @@ -0,0 +1,93 @@ +package tracers + +import ( + "context" + "math/big" + "sync/atomic" + "testing" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +func (b *testBackend) BlockChain() *core.BlockChain { + return b.chain +} + +func TestProvingPreflights(t *testing.T) { + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks := 50 + signer := types.HomesteadSigner{} + + var ( + ref atomic.Uint32 // total refs has made + rel atomic.Uint32 // total rels has made + nonce uint64 + ) + backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + for j := 0; j < i+1; j++ { + tx, _ := types.SignTx(types.NewTransaction(nonce, accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + nonce += 1 + } + }) + backend.refHook = func() { ref.Add(1) } + backend.relHook = func() { rel.Add(1) } + api := NewAPI(backend) + + // single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}` + var cases = []struct { + start uint64 + end uint64 + config *TraceConfig + }{ + {0, 50, nil}, // the entire chain range, blocks [1, 50] + {10, 20, nil}, // the middle chain range, blocks [11, 20] + } + for _, c := range cases { + ref.Store(0) + rel.Store(0) + + from, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.start)) + to, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.end)) + resCh := api.provingPreflights(from, to, c.config, nil) + + next := c.start + 1 + for result := range resCh { + if have, want := result.Block.NumberU64(), next; have != want { + t.Fatalf("unexpected tracing block, have %d want %d", have, want) + } + if next == 1 { + if have, want := len(result.InitAccountProofs), 2; have != want { + t.Fatalf("unexpected result length, have %d want %d", have, want) + } + } else { + if have, want := len(result.InitAccountProofs), 3; have != want { + t.Fatalf("unexpected result length, have %d want %d", have, want) + } + } + next += 1 + } + if next != c.end+1 { + t.Error("Missing tracing block") + } + + if nref, nrel := ref.Load(), rel.Load(); nref != nrel { + t.Errorf("Ref and deref actions are not equal, ref %d rel %d", nref, nrel) + } + } +}