From 4af65a5bf33942284ea7b14dcdd592026c70c965 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Tue, 13 Jan 2026 15:53:54 +0100 Subject: [PATCH 1/6] Start working on TestReExecution --- cmd/utils/app/backtest_commitment_cmd.go | 2 +- cmd/utils/app/snapshots_cmd.go | 24 +++--- cmd/utils/app/squeeze_cmd.go | 6 +- execution/exec/backtester/back_test.go | 91 +++++++++++++++++++++++ execution/exec/historical_trace_worker.go | 1 + 5 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 execution/exec/backtester/back_test.go diff --git a/cmd/utils/app/backtest_commitment_cmd.go b/cmd/utils/app/backtest_commitment_cmd.go index b33201bc1a3..501749f2424 100644 --- a/cmd/utils/app/backtest_commitment_cmd.go +++ b/cmd/utils/app/backtest_commitment_cmd.go @@ -107,7 +107,7 @@ func doBacktestCommitment(ctx context.Context, args backtestCommitmentArgs, logg defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - snaps, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + snaps, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) if err != nil { return err } diff --git a/cmd/utils/app/snapshots_cmd.go b/cmd/utils/app/snapshots_cmd.go index 6bb046a64cd..972c37f0e24 100644 --- a/cmd/utils/app/snapshots_cmd.go +++ b/cmd/utils/app/snapshots_cmd.go @@ -824,7 +824,7 @@ func doRollbackSnapshotsToBlock(ctx context.Context, blockNum uint64, prompt boo defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) br, agg := res.BlockRetire, res.Aggregator if err != nil { return err @@ -977,7 +977,7 @@ func doDebugKey(cliCtx *cli.Context) error { chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) agg := res.Aggregator if err != nil { return err @@ -1049,7 +1049,7 @@ func doIntegrity(cliCtx *cli.Context) error { chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) borSnaps, blockRetire, agg := res.BorSnaps, res.BlockRetire, res.Aggregator if err != nil { return err @@ -1162,7 +1162,7 @@ func doCheckCommitmentHistAtBlk(cliCtx *cli.Context, logger log.Logger) error { defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false /*keepBlocks*/, true /*produceE2*/, true /*produceE3*/, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) blockRetire, agg := res.BlockRetire, res.Aggregator if err != nil { return err @@ -1187,7 +1187,7 @@ func doCheckCommitmentHistAtBlkRange(cliCtx *cli.Context, logger log.Logger) err defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false /*keepBlocks*/, true /*produceE2*/, true /*produceE3*/, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) blockRetire, agg := res.BlockRetire, res.Aggregator if err != nil { return err @@ -1754,7 +1754,7 @@ func doBlkTxNum(cliCtx *cli.Context) error { chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) br, agg := res.BlockRetire, res.Aggregator if err != nil { return err @@ -1965,7 +1965,7 @@ func doIndicesCommand(cliCtx *cli.Context, dirs datadir.Dirs) error { chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) caplinSnaps, caplinStateSnaps, br, agg := res.CaplinSnaps, res.CaplinStateSnaps, res.BlockRetire, res.Aggregator if err != nil { return err @@ -2006,7 +2006,7 @@ func doLS(cliCtx *cli.Context, dirs datadir.Dirs) error { defer chainDB.Close() cfg := ethconfig.NewSnapCfg(false, true, true, fromdb.ChainConfig(chainDB).ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) blockSnaps, borSnaps, caplinSnaps, agg := res.BlockSnaps, res.BorSnaps, res.CaplinSnaps, res.Aggregator if err != nil { return err @@ -2031,7 +2031,7 @@ type OpenSnapsResult struct { ForkAgg *state.ForkableAgg } -func openSnaps(ctx context.Context, cfg ethconfig.BlocksFreezing, dirs datadir.Dirs, chainDB kv.RwDB, logger log.Logger) ( +func OpenSnaps(ctx context.Context, cfg ethconfig.BlocksFreezing, dirs datadir.Dirs, chainDB kv.RwDB, logger log.Logger) ( res OpenSnapsResult, clean func(), err error, @@ -2306,7 +2306,7 @@ func doRemoveOverlap(cliCtx *cli.Context, dirs datadir.Dirs) error { cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) ctx := cliCtx.Context - res, clean, err := openSnaps(ctx, cfg, dirs, db, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, db, logger) agg := res.Aggregator if err != nil { return err @@ -2437,7 +2437,7 @@ func doUnmerge(cliCtx *cli.Context, dirs datadir.Dirs) error { defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, chainDB, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, chainDB, logger) br := res.BlockRetire if err != nil { return err @@ -2464,7 +2464,7 @@ func doRetireCommand(cliCtx *cli.Context, dirs datadir.Dirs) error { chainConfig := fromdb.ChainConfig(db) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, db, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, db, logger) caplinSnaps, br, agg := res.CaplinSnaps, res.BlockRetire, res.Aggregator if err != nil { return err diff --git a/cmd/utils/app/squeeze_cmd.go b/cmd/utils/app/squeeze_cmd.go index f7d08b4cefb..718d1ee722b 100644 --- a/cmd/utils/app/squeeze_cmd.go +++ b/cmd/utils/app/squeeze_cmd.go @@ -90,7 +90,7 @@ func squeezeCommitment(ctx context.Context, dirs datadir.Dirs, logger log.Logger defer db.Close() cfg := ethconfig.NewSnapCfg(false, true, true, fromdb.ChainConfig(db).ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, db, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, db, logger) agg := res.Aggregator if err != nil { return err @@ -122,7 +122,7 @@ func squeezeStorage(ctx context.Context, dirs datadir.Dirs, logger log.Logger) e db := dbCfg(dbcfg.ChainDB, dirs.Chaindata).MustOpen() defer db.Close() cfg := ethconfig.NewSnapCfg(false, true, true, fromdb.ChainConfig(db).ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, db, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, db, logger) agg := res.Aggregator if err != nil { return err @@ -233,7 +233,7 @@ func squeezeBlocks(ctx context.Context, dirs datadir.Dirs, logger log.Logger) er chainConfig := fromdb.ChainConfig(db) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) - res, clean, err := openSnaps(ctx, cfg, dirs, db, logger) + res, clean, err := OpenSnaps(ctx, cfg, dirs, db, logger) br := res.BlockRetire if err != nil { return err diff --git a/execution/exec/backtester/back_test.go b/execution/exec/backtester/back_test.go new file mode 100644 index 00000000000..ee4e856f4d9 --- /dev/null +++ b/execution/exec/backtester/back_test.go @@ -0,0 +1,91 @@ +package exec_backtester + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/semaphore" + + "github.com/erigontech/erigon/cmd/hack/tool/fromdb" + "github.com/erigontech/erigon/cmd/utils/app" + "github.com/erigontech/erigon/common/log/v3" + "github.com/erigontech/erigon/db/datadir" + "github.com/erigontech/erigon/db/kv" + "github.com/erigontech/erigon/db/kv/dbcfg" + "github.com/erigontech/erigon/db/kv/mdbx" + "github.com/erigontech/erigon/db/kv/order" + "github.com/erigontech/erigon/db/kv/temporal" + "github.com/erigontech/erigon/node/ethconfig" +) + +func dbCfg(label kv.Label, path string) mdbx.MdbxOpts { + const ThreadsLimit = 9_000 + limiterB := semaphore.NewWeighted(ThreadsLimit) + return mdbx.New(label, log.New()).Path(path). + RoTxsLimiter(limiterB). + Accede(true) // integration tool: open db without creation and without blocking erigon +} + +func TestReExecution(t *testing.T) { + // https://github.com/erigontech/erigon/issues/18276 + dataDir := "/Users/andrew/Library/Erigon/chiado" + blockNum := uint64(19366160) + + dirs, _, err := datadir.New(dataDir).MustFlock() + require.NoError(t, err) + // defer func() { + // err := l.Unlock() + // require.NoError(t, err) + // }() + chainDB := dbCfg(dbcfg.ChainDB, dirs.Chaindata).MustOpen() + //defer chainDB.Close() + chainConfig := fromdb.ChainConfig(chainDB) + cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) + ctx := context.TODO() + snaps, _, err := app.OpenSnaps(ctx, cfg, dirs, chainDB, log.New()) + require.NoError(t, err) + // defer clean() + blockReader, _ := snaps.BlockRetire.IO() + db, err := temporal.New(chainDB, snaps.Aggregator) + require.NoError(t, err) + // defer db.Close() + + tnr := blockReader.TxnumReader(ctx) + tx, err := db.BeginTemporalRo(ctx) + require.NoError(t, err) + fromTxNum, err := tnr.Min(tx, blockNum) + require.NoError(t, err) + + // Check changes at the beginning of the block + changes := 0 + it, err := tx.HistoryRange(kv.AccountsDomain, int(fromTxNum), int(fromTxNum+1), order.Asc, -1) + require.NoError(t, err) + //defer it.Close() + for it.HasNext() { + _, _, err := it.Next() + require.NoError(t, err) + changes++ + } + assert.Equal(t, 0, changes) + + changes = 0 + it, err = tx.HistoryRange(kv.StorageDomain, int(fromTxNum), int(fromTxNum+1), order.Asc, -1) + require.NoError(t, err) + //defer it.Close() + for it.HasNext() { + _, _, err := it.Next() + require.NoError(t, err) + changes++ + } + assert.Equal(t, 0, changes) + + // Alternative: use HistoricalTraceWorker + + // TODO: re-execute and compare results + // ? + // block, err := blockReader.BlockByNumber(ctx, tx, blockNum) + // require.NoError(t, err) + +} diff --git a/execution/exec/historical_trace_worker.go b/execution/exec/historical_trace_worker.go index 7d4ff4ada87..16aedf9a2aa 100644 --- a/execution/exec/historical_trace_worker.go +++ b/execution/exec/historical_trace_worker.go @@ -661,6 +661,7 @@ func CustomTraceMapReduce(ctx context.Context, fromBlock, toBlock uint64, consum return nil } +// TODO(yperbasis): why is it called "WithSenders"? func BlockWithSenders(ctx context.Context, db kv.RoDB, tx kv.Tx, blockReader services.BlockReader, blockNum uint64) (b *types.Block, err error) { select { case <-ctx.Done(): From b2bfe0d7143246349cff53ecff30c118f5f28807 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Tue, 20 Jan 2026 14:35:19 +0100 Subject: [PATCH 2/6] Draft implementation of reExecutedChanges --- execution/exec/backtester/back_test.go | 118 ++++++++++++++++++------- execution/tests/testutil/map_writer.go | 70 +++++++++++++++ 2 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 execution/tests/testutil/map_writer.go diff --git a/execution/exec/backtester/back_test.go b/execution/exec/backtester/back_test.go index ee4e856f4d9..6835057c1e8 100644 --- a/execution/exec/backtester/back_test.go +++ b/execution/exec/backtester/back_test.go @@ -11,23 +11,97 @@ import ( "github.com/erigontech/erigon/cmd/hack/tool/fromdb" "github.com/erigontech/erigon/cmd/utils/app" "github.com/erigontech/erigon/common/log/v3" + "github.com/erigontech/erigon/db/consensuschain" "github.com/erigontech/erigon/db/datadir" "github.com/erigontech/erigon/db/kv" "github.com/erigontech/erigon/db/kv/dbcfg" "github.com/erigontech/erigon/db/kv/mdbx" "github.com/erigontech/erigon/db/kv/order" "github.com/erigontech/erigon/db/kv/temporal" + "github.com/erigontech/erigon/db/services" + "github.com/erigontech/erigon/execution/chain" + "github.com/erigontech/erigon/execution/protocol" + "github.com/erigontech/erigon/execution/state" + "github.com/erigontech/erigon/execution/tests/testutil" + "github.com/erigontech/erigon/execution/types" + "github.com/erigontech/erigon/execution/types/accounts" + "github.com/erigontech/erigon/execution/vm" "github.com/erigontech/erigon/node/ethconfig" + "github.com/erigontech/erigon/node/rulesconfig" ) -func dbCfg(label kv.Label, path string) mdbx.MdbxOpts { +func dbCfg(label kv.Label, path string, logger log.Logger) mdbx.MdbxOpts { const ThreadsLimit = 9_000 limiterB := semaphore.NewWeighted(ThreadsLimit) - return mdbx.New(label, log.New()).Path(path). + return mdbx.New(label, logger).Path(path). RoTxsLimiter(limiterB). Accede(true) // integration tool: open db without creation and without blocking erigon } +func storedChanges(tx kv.TemporalTx, fromTxNum uint64, txnIndex int) (map[string]([]byte), error) { + changes := make(map[string]([]byte)) + + it, err := tx.HistoryRange(kv.AccountsDomain, int(fromTxNum+uint64(txnIndex+1)), int(fromTxNum+uint64(txnIndex+2)), order.Asc, -1) + if err != nil { + return changes, err + } + //defer it.Close() + for it.HasNext() { + key, val, err := it.Next() + if err != nil { + return changes, err + } + changes[string(key)] = val + } + it, err = tx.HistoryRange(kv.StorageDomain, int(fromTxNum+uint64(txnIndex+1)), int(fromTxNum+uint64(txnIndex+2)), order.Asc, -1) + if err != nil { + return changes, err + } + //defer it.Close() + for it.HasNext() { + key, val, err := it.Next() + if err != nil { + return changes, err + } + changes[string(key)] = val + } + return changes, err +} + +// Use HistoricalTraceWorker instead??? +func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader services.FullBlockReader, chainConfig *chain.Config, header *types.Header, fromTxNum uint64, txnIndex int, logger log.Logger) (map[string]([]byte), error) { + // What's the diff between commitmentdb.HistoryStateReader and state.HistoryReaderV3? + stateReader := state.NewHistoryReaderV3(tx, fromTxNum+uint64(txnIndex+1)) + ibs := state.New(stateReader) + engine := rulesconfig.CreateRulesEngineBareBones(ctx, chainConfig, logger) + vmCfg := vm.Config{} + syscall := func(contract accounts.Address, data []byte, ibs *state.IntraBlockState, header *types.Header, constCall bool) ([]byte, error) { + ret, err := protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, constCall /* constCall */, vmCfg) + return ret, err + } + chain := consensuschain.NewReader(chainConfig, tx, blockReader, logger) + err := engine.Initialize(chainConfig, chain, header, ibs, syscall, logger, nil /* hooks */) + if err != nil { + return nil, err + } + noop := state.NewNoopWriter() + blockContext := protocol.NewEVMBlockContext(header, protocol.GetHashFn(header, nil), engine, accounts.NilAddress, chainConfig) + rules := blockContext.Rules(chainConfig) + err = ibs.FinalizeTx(rules, noop) + if err != nil { + return nil, err + } + stateWriter := testutil.NewMapWriter() + err = ibs.MakeWriteSet(rules, stateWriter) + if err != nil { + return nil, err + } + + // TODO(yperbasis) implement for txnIndex != -1 + + return stateWriter.Changes, err +} + func TestReExecution(t *testing.T) { // https://github.com/erigontech/erigon/issues/18276 dataDir := "/Users/andrew/Library/Erigon/chiado" @@ -39,12 +113,13 @@ func TestReExecution(t *testing.T) { // err := l.Unlock() // require.NoError(t, err) // }() - chainDB := dbCfg(dbcfg.ChainDB, dirs.Chaindata).MustOpen() + logger := log.New() + chainDB := dbCfg(dbcfg.ChainDB, dirs.Chaindata, logger).MustOpen() //defer chainDB.Close() chainConfig := fromdb.ChainConfig(chainDB) cfg := ethconfig.NewSnapCfg(false, true, true, chainConfig.ChainName) ctx := context.TODO() - snaps, _, err := app.OpenSnaps(ctx, cfg, dirs, chainDB, log.New()) + snaps, _, err := app.OpenSnaps(ctx, cfg, dirs, chainDB, logger) require.NoError(t, err) // defer clean() blockReader, _ := snaps.BlockRetire.IO() @@ -58,34 +133,17 @@ func TestReExecution(t *testing.T) { fromTxNum, err := tnr.Min(tx, blockNum) require.NoError(t, err) - // Check changes at the beginning of the block - changes := 0 - it, err := tx.HistoryRange(kv.AccountsDomain, int(fromTxNum), int(fromTxNum+1), order.Asc, -1) + block, err := blockReader.BlockByNumber(ctx, tx, blockNum) require.NoError(t, err) - //defer it.Close() - for it.HasNext() { - _, _, err := it.Next() - require.NoError(t, err) - changes++ - } - assert.Equal(t, 0, changes) - changes = 0 - it, err = tx.HistoryRange(kv.StorageDomain, int(fromTxNum), int(fromTxNum+1), order.Asc, -1) - require.NoError(t, err) - //defer it.Close() - for it.HasNext() { - _, _, err := it.Next() - require.NoError(t, err) - changes++ - } - assert.Equal(t, 0, changes) - - // Alternative: use HistoricalTraceWorker + txnIndex := -1 - // TODO: re-execute and compare results - // ? - // block, err := blockReader.BlockByNumber(ctx, tx, blockNum) - // require.NoError(t, err) + // Check changes at the beginning of the block + changesInDb, err := storedChanges(tx, fromTxNum, txnIndex) + require.NoError(t, err) + reExecChanges, err := reExecutedChanges(ctx, tx, blockReader, chainConfig, block.Header(), fromTxNum, txnIndex, logger) + require.NoError(t, err) + assert.Equal(t, changesInDb, reExecChanges) + // TODO: re-execute and compare results for all transactions in the block } diff --git a/execution/tests/testutil/map_writer.go b/execution/tests/testutil/map_writer.go new file mode 100644 index 00000000000..14a6b47fd3d --- /dev/null +++ b/execution/tests/testutil/map_writer.go @@ -0,0 +1,70 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package testutil + +import ( + "github.com/holiman/uint256" + + "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/execution/state" + "github.com/erigontech/erigon/execution/types/accounts" +) + +var ( + _ state.StateWriter = (*MapWriter)(nil) +) + +type MapWriter struct { + Changes map[string]([]byte) +} + +func NewMapWriter() *MapWriter { + return &MapWriter{Changes: make(map[string]([]byte))} +} + +func (mw *MapWriter) UpdateAccountData(address accounts.Address, original, account *accounts.Account) error { + addressValue := address.Value() + mw.Changes[string(addressValue[:])] = accounts.SerialiseV3(account) + return nil +} + +func (mw *MapWriter) UpdateAccountCode(address accounts.Address, incarnation uint64, codeHash accounts.CodeHash, code []byte) error { + return nil +} + +func (mw *MapWriter) DeleteAccount(address accounts.Address, original *accounts.Account) error { + return nil +} + +func (mw *MapWriter) WriteAccountStorage(address accounts.Address, incarnation uint64, key accounts.StorageKey, original, value uint256.Int) error { + var addressValue common.Address + if !address.IsNil() { + addressValue = address.Value() + } + var keyValue common.Hash + if !key.IsNil() { + keyValue = key.Value() + } + composite := append(addressValue[:], keyValue[:]...) + v := value.Bytes() + mw.Changes[string(composite)] = v + return nil +} + +func (mw *MapWriter) CreateContract(address accounts.Address) error { + return nil +} From 3ffe81dbd7113bee2eff7400b58c4359b5142641 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Tue, 20 Jan 2026 17:31:39 +0100 Subject: [PATCH 3/6] make beginning of the block check pass --- execution/exec/backtester/back_test.go | 23 ++++++++++++++++++++--- execution/tests/testutil/map_writer.go | 3 +++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/execution/exec/backtester/back_test.go b/execution/exec/backtester/back_test.go index 6835057c1e8..59ff6286406 100644 --- a/execution/exec/backtester/back_test.go +++ b/execution/exec/backtester/back_test.go @@ -2,6 +2,7 @@ package exec_backtester import ( "context" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -47,24 +48,40 @@ func storedChanges(tx kv.TemporalTx, fromTxNum uint64, txnIndex int) (map[string } //defer it.Close() for it.HasNext() { - key, val, err := it.Next() + key, _, err := it.Next() if err != nil { return changes, err } + val, ok, err := tx.GetAsOf(kv.AccountsDomain, key, fromTxNum+uint64(txnIndex+2)) + if err != nil { + return changes, err + } + if !ok { + return changes, errors.New("account val not found") + } changes[string(key)] = val } + it, err = tx.HistoryRange(kv.StorageDomain, int(fromTxNum+uint64(txnIndex+1)), int(fromTxNum+uint64(txnIndex+2)), order.Asc, -1) if err != nil { return changes, err } //defer it.Close() for it.HasNext() { - key, val, err := it.Next() + key, _, err := it.Next() if err != nil { return changes, err } + val, ok, err := tx.GetAsOf(kv.StorageDomain, key, fromTxNum+uint64(txnIndex+2)) + if err != nil { + return changes, err + } + if !ok { + return changes, errors.New("storage val not found") + } changes[string(key)] = val } + return changes, err } @@ -136,7 +153,7 @@ func TestReExecution(t *testing.T) { block, err := blockReader.BlockByNumber(ctx, tx, blockNum) require.NoError(t, err) - txnIndex := -1 + txnIndex := -1 // beginning of the block // Check changes at the beginning of the block changesInDb, err := storedChanges(tx, fromTxNum, txnIndex) diff --git a/execution/tests/testutil/map_writer.go b/execution/tests/testutil/map_writer.go index 14a6b47fd3d..cc82c22c3d8 100644 --- a/execution/tests/testutil/map_writer.go +++ b/execution/tests/testutil/map_writer.go @@ -37,6 +37,9 @@ func NewMapWriter() *MapWriter { } func (mw *MapWriter) UpdateAccountData(address accounts.Address, original, account *accounts.Account) error { + if account.Equals(original) { + return nil + } addressValue := address.Value() mw.Changes[string(addressValue[:])] = accounts.SerialiseV3(account) return nil From e6d54447c6276636e8449fd61afa1de4b6729618 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Wed, 21 Jan 2026 12:51:22 +0100 Subject: [PATCH 4/6] Small blob gas fix in HistoricalTraceWorker --- execution/exec/historical_trace_worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/exec/historical_trace_worker.go b/execution/exec/historical_trace_worker.go index 16aedf9a2aa..c021a235791 100644 --- a/execution/exec/historical_trace_worker.go +++ b/execution/exec/historical_trace_worker.go @@ -202,7 +202,7 @@ func (rw *HistoricalTraceWorker) RunTxTask(txTask *TxTask) *TxResult { default: tracer := calltracer.NewCallTracer(nil) result.Err = func() error { - rw.taskGasPool.Reset(txTask.Tx().GetGasLimit(), cc.GetMaxBlobGasPerBlock(header.Time)) + rw.taskGasPool.Reset(txTask.Tx().GetGasLimit(), txTask.Tx().GetBlobGas()) rw.vmCfg.Tracer = tracer.Tracer().Hooks ibs.SetTxContext(txTask.BlockNumber(), txTask.TxIndex) txn := txTask.Tx() From 96c24c1c792164940443a2f1e2b42cf39ca1e1b4 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Wed, 21 Jan 2026 12:57:48 +0100 Subject: [PATCH 5/6] implement reExecutedChanges for normal transactions --- execution/exec/backtester/back_test.go | 76 ++++++++++++++++---------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/execution/exec/backtester/back_test.go b/execution/exec/backtester/back_test.go index 59ff6286406..972d84cc07e 100644 --- a/execution/exec/backtester/back_test.go +++ b/execution/exec/backtester/back_test.go @@ -3,6 +3,7 @@ package exec_backtester import ( "context" "errors" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -86,36 +87,53 @@ func storedChanges(tx kv.TemporalTx, fromTxNum uint64, txnIndex int) (map[string } // Use HistoricalTraceWorker instead??? -func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader services.FullBlockReader, chainConfig *chain.Config, header *types.Header, fromTxNum uint64, txnIndex int, logger log.Logger) (map[string]([]byte), error) { - // What's the diff between commitmentdb.HistoryStateReader and state.HistoryReaderV3? +func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader services.FullBlockReader, chainConfig *chain.Config, block *types.Block, fromTxNum uint64, txnIndex int, logger log.Logger) (map[string]([]byte), error) { + header := block.Header() stateReader := state.NewHistoryReaderV3(tx, fromTxNum+uint64(txnIndex+1)) ibs := state.New(stateReader) engine := rulesconfig.CreateRulesEngineBareBones(ctx, chainConfig, logger) - vmCfg := vm.Config{} - syscall := func(contract accounts.Address, data []byte, ibs *state.IntraBlockState, header *types.Header, constCall bool) ([]byte, error) { - ret, err := protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, constCall /* constCall */, vmCfg) - return ret, err - } - chain := consensuschain.NewReader(chainConfig, tx, blockReader, logger) - err := engine.Initialize(chainConfig, chain, header, ibs, syscall, logger, nil /* hooks */) - if err != nil { - return nil, err - } - noop := state.NewNoopWriter() blockContext := protocol.NewEVMBlockContext(header, protocol.GetHashFn(header, nil), engine, accounts.NilAddress, chainConfig) rules := blockContext.Rules(chainConfig) - err = ibs.FinalizeTx(rules, noop) - if err != nil { - return nil, err + vmCfg := vm.Config{} + + if txnIndex == -1 { // beginning of the block + syscall := func(contract accounts.Address, data []byte, ibs *state.IntraBlockState, header *types.Header, constCall bool) ([]byte, error) { + ret, err := protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, constCall /* constCall */, vmCfg) + return ret, err + } + chain := consensuschain.NewReader(chainConfig, tx, blockReader, logger) + err := engine.Initialize(chainConfig, chain, header, ibs, syscall, logger, nil /* hooks */) + if err != nil { + return nil, err + } + noop := state.NewNoopWriter() + err = ibs.FinalizeTx(rules, noop) + if err != nil { + return nil, err + } + } else { // normal transaction + txn := block.Transactions()[txnIndex] + gasPool := protocol.NewGasPool(txn.GetGasLimit(), txn.GetBlobGas()) + ibs.SetTxContext(block.NumberU64(), txnIndex) + signer := types.MakeSigner(chainConfig, block.NumberU64(), header.Time) + msg, err := txn.AsMessage(*signer, header.BaseFee, rules) + if err != nil { + return nil, err + } + txnContext := protocol.NewEVMTxContext(msg) + evm := vm.NewEVM(blockContext, txnContext, ibs, chainConfig, vmCfg) + _, err = protocol.ApplyMessage(evm, msg, gasPool, true /* refunds */, false /* gasBailout */, engine) + if err != nil { + return nil, err + } + ibs.SoftFinalise() } + stateWriter := testutil.NewMapWriter() - err = ibs.MakeWriteSet(rules, stateWriter) + err := ibs.MakeWriteSet(rules, stateWriter) if err != nil { return nil, err } - - // TODO(yperbasis) implement for txnIndex != -1 - return stateWriter.Changes, err } @@ -153,14 +171,12 @@ func TestReExecution(t *testing.T) { block, err := blockReader.BlockByNumber(ctx, tx, blockNum) require.NoError(t, err) - txnIndex := -1 // beginning of the block - - // Check changes at the beginning of the block - changesInDb, err := storedChanges(tx, fromTxNum, txnIndex) - require.NoError(t, err) - reExecChanges, err := reExecutedChanges(ctx, tx, blockReader, chainConfig, block.Header(), fromTxNum, txnIndex, logger) - require.NoError(t, err) - assert.Equal(t, changesInDb, reExecChanges) - - // TODO: re-execute and compare results for all transactions in the block + // TODO(yperbasis) check block end + for txnIndex := -1; txnIndex < len(block.Transactions()); txnIndex++ { + changesInDb, err := storedChanges(tx, fromTxNum, txnIndex) + require.NoError(t, err) + reExecChanges, err := reExecutedChanges(ctx, tx, blockReader, chainConfig, block, fromTxNum, txnIndex, logger) + require.NoError(t, err) + assert.Equal(t, changesInDb, reExecChanges, fmt.Sprintf("mismatch at %d", txnIndex)) + } } From c867f82a2b0631040b3629d88374e7fabc84f0f9 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Wed, 21 Jan 2026 14:40:35 +0100 Subject: [PATCH 6/6] implement end of the block --- execution/exec/backtester/back_test.go | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/execution/exec/backtester/back_test.go b/execution/exec/backtester/back_test.go index 972d84cc07e..9ae94fbf04e 100644 --- a/execution/exec/backtester/back_test.go +++ b/execution/exec/backtester/back_test.go @@ -87,7 +87,7 @@ func storedChanges(tx kv.TemporalTx, fromTxNum uint64, txnIndex int) (map[string } // Use HistoricalTraceWorker instead??? -func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader services.FullBlockReader, chainConfig *chain.Config, block *types.Block, fromTxNum uint64, txnIndex int, logger log.Logger) (map[string]([]byte), error) { +func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader services.FullBlockReader, chainConfig *chain.Config, block *types.Block, fromTxNum uint64, txnIndex int, logger log.Logger, receipts types.Receipts) (map[string]([]byte), error) { header := block.Header() stateReader := state.NewHistoryReaderV3(tx, fromTxNum+uint64(txnIndex+1)) ibs := state.New(stateReader) @@ -98,11 +98,10 @@ func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader servic if txnIndex == -1 { // beginning of the block syscall := func(contract accounts.Address, data []byte, ibs *state.IntraBlockState, header *types.Header, constCall bool) ([]byte, error) { - ret, err := protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, constCall /* constCall */, vmCfg) - return ret, err + return protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, constCall /* constCall */, vmCfg) } - chain := consensuschain.NewReader(chainConfig, tx, blockReader, logger) - err := engine.Initialize(chainConfig, chain, header, ibs, syscall, logger, nil /* hooks */) + chainReader := consensuschain.NewReader(chainConfig, tx, blockReader, logger) + err := engine.Initialize(chainConfig, chainReader, header, ibs, syscall, logger, nil /* hooks */) if err != nil { return nil, err } @@ -111,6 +110,18 @@ func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader servic if err != nil { return nil, err } + } else if txnIndex == len(block.Transactions()) { // end of the block + ibs.SetTxContext(block.NumberU64(), txnIndex) + syscall := func(contract accounts.Address, data []byte) ([]byte, error) { + return protocol.SysCallContract(contract, data, chainConfig, ibs, header, engine, false /* constCall */, vmCfg) + } + chainReader := consensuschain.NewReader(chainConfig, tx, blockReader, logger) + _, err := engine.Finalize( + chainConfig, header, ibs, block.Transactions(), block.Uncles(), + receipts, block.Withdrawals(), chainReader, syscall, false, logger) + if err != nil { + return nil, err + } } else { // normal transaction txn := block.Transactions()[txnIndex] gasPool := protocol.NewGasPool(txn.GetGasLimit(), txn.GetBlobGas()) @@ -127,6 +138,8 @@ func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader servic return nil, err } ibs.SoftFinalise() + logs := ibs.GetRawLogs(txnIndex) + receipts = append(receipts, &types.Receipt{Logs: logs}) } stateWriter := testutil.NewMapWriter() @@ -139,7 +152,7 @@ func reExecutedChanges(ctx context.Context, tx kv.TemporalTx, blockReader servic func TestReExecution(t *testing.T) { // https://github.com/erigontech/erigon/issues/18276 - dataDir := "/Users/andrew/Library/Erigon/chiado" + dataDir := "/Users/andrew/Library/Erigon/chiado_backup" blockNum := uint64(19366160) dirs, _, err := datadir.New(dataDir).MustFlock() @@ -171,11 +184,11 @@ func TestReExecution(t *testing.T) { block, err := blockReader.BlockByNumber(ctx, tx, blockNum) require.NoError(t, err) - // TODO(yperbasis) check block end - for txnIndex := -1; txnIndex < len(block.Transactions()); txnIndex++ { + receipts := make([]*types.Receipt, 0, len(block.Transactions())) + for txnIndex := -1; txnIndex <= len(block.Transactions()); txnIndex++ { changesInDb, err := storedChanges(tx, fromTxNum, txnIndex) require.NoError(t, err) - reExecChanges, err := reExecutedChanges(ctx, tx, blockReader, chainConfig, block, fromTxNum, txnIndex, logger) + reExecChanges, err := reExecutedChanges(ctx, tx, blockReader, chainConfig, block, fromTxNum, txnIndex, logger, receipts) require.NoError(t, err) assert.Equal(t, changesInDb, reExecChanges, fmt.Sprintf("mismatch at %d", txnIndex)) }