Skip to content

Commit

Permalink
feat(taiko): implement ApplyTransactionWithTimeout for transaction ex…
Browse files Browse the repository at this point in the history
…ecution with context timeout
  • Loading branch information
johntaiko committed Jan 22, 2025
1 parent 2f0956c commit 0e5a380
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 29 deletions.
55 changes: 55 additions & 0 deletions core/taiko_state_processor.go
Original file line number Diff line number Diff line change
@@ -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)
}
67 changes: 38 additions & 29 deletions eth/tracers/taiko_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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{},
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
93 changes: 93 additions & 0 deletions eth/tracers/taiko_api_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 0e5a380

Please sign in to comment.