diff --git a/cmd/headers/download/downloader.go b/cmd/headers/download/downloader.go index 71f5c7e54..36d5ec26b 100644 --- a/cmd/headers/download/downloader.go +++ b/cmd/headers/download/downloader.go @@ -332,6 +332,7 @@ func NewStagedSync( stagedsync.StageExecuteBlocksCfg( db, sm.Receipts, + sm.CallTraces, batchSize, nil, nil, diff --git a/cmd/integration/commands/snapshot_check.go b/cmd/integration/commands/snapshot_check.go index b5dab590d..84409cb96 100644 --- a/cmd/integration/commands/snapshot_check.go +++ b/cmd/integration/commands/snapshot_check.go @@ -246,7 +246,7 @@ func snapshotCheck(ctx context.Context, db ethdb.Database, isNew bool, tmpDir st log.Info("Stage4", "progress", stage4.BlockNumber) err = stagedsync.SpawnExecuteBlocksStage(stage4, tx.(ethdb.HasTx).Tx().(ethdb.RwTx), blockNumber, ch, - stagedsync.StageExecuteBlocksCfg(kv, false, batchSize, nil, nil, nil, nil, chainConfig, engine, vmConfig, tmpDir), + stagedsync.StageExecuteBlocksCfg(kv, false, false, batchSize, nil, nil, nil, nil, chainConfig, engine, vmConfig, tmpDir), ) if err != nil { return fmt.Errorf("execution err %w", err) diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go index 1c8bbd9f4..2e9e860e5 100644 --- a/cmd/integration/commands/stages.go +++ b/cmd/integration/commands/stages.go @@ -443,7 +443,7 @@ func stageExec(db ethdb.Database, ctx context.Context) error { stage4 := stage(sync, tx, stages.Execution) log.Info("Stage4", "progress", stage4.BlockNumber) ch := ctx.Done() - cfg := stagedsync.StageExecuteBlocksCfg(kv, sm.Receipts, batchSize, nil, nil, silkwormExecutionFunc(), nil, chainConfig, engine, vmConfig, tmpDBPath) + cfg := stagedsync.StageExecuteBlocksCfg(kv, sm.Receipts, sm.CallTraces, batchSize, nil, nil, silkwormExecutionFunc(), nil, chainConfig, engine, vmConfig, tmpDBPath) if unwind > 0 { u := &stagedsync.UnwindState{Stage: stages.Execution, UnwindPoint: stage4.BlockNumber - unwind} err = stagedsync.UnwindExecutionStage(u, stage4, tx, ch, cfg) diff --git a/cmd/integration/commands/state_stages.go b/cmd/integration/commands/state_stages.go index a266f2ba4..64e6002e4 100644 --- a/cmd/integration/commands/state_stages.go +++ b/cmd/integration/commands/state_stages.go @@ -170,7 +170,7 @@ func syncBySmallSteps(db ethdb.Database, miningConfig params.MiningConfig, ctx c } sm, engine, chainConfig, vmConfig, txPool, st, mining := newSync2(db, tx) - execCfg := stagedsync.StageExecuteBlocksCfg(kv, sm.Receipts, batchSize, nil, nil, nil, changeSetHook, chainConfig, engine, vmConfig, tmpDir) + execCfg := stagedsync.StageExecuteBlocksCfg(kv, sm.Receipts, sm.CallTraces, batchSize, nil, nil, nil, changeSetHook, chainConfig, engine, vmConfig, tmpDir) execUntilFunc := func(execToBlock uint64) func(stageState *stagedsync.StageState, unwinder stagedsync.Unwinder) error { return func(s *stagedsync.StageState, unwinder stagedsync.Unwinder) error { @@ -515,7 +515,7 @@ func loopExec(db ethdb.Database, ctx context.Context, unwind uint64) error { from := progress(tx, stages.Execution) to := from + unwind - cfg := stagedsync.StageExecuteBlocksCfg(kv, true, batchSize, nil, nil, silkwormExecutionFunc(), nil, chainConfig, engine, vmConfig, tmpDBPath) + cfg := stagedsync.StageExecuteBlocksCfg(kv, true, false, batchSize, nil, nil, silkwormExecutionFunc(), nil, chainConfig, engine, vmConfig, tmpDBPath) // set block limit of execute stage sync.MockExecFunc(stages.Execution, func(stageState *stagedsync.StageState, unwinder stagedsync.Unwinder) error { diff --git a/cmd/rpcdaemon/commands/trace_adhoc.go b/cmd/rpcdaemon/commands/trace_adhoc.go index 457f2907f..672712e47 100644 --- a/cmd/rpcdaemon/commands/trace_adhoc.go +++ b/cmd/rpcdaemon/commands/trace_adhoc.go @@ -241,6 +241,8 @@ func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time. topTrace.Error = "Out of gas" case vm.ErrExecutionReverted: topTrace.Error = "Reverted" + case vm.ErrWriteProtection: + topTrace.Error = "Mutable Call In Static Context" default: switch err.(type) { case *vm.ErrStackUnderflow: diff --git a/cmd/rpcdaemon/commands/trace_filtering.go b/cmd/rpcdaemon/commands/trace_filtering.go index 5ca23ffed..1d29ccbf5 100644 --- a/cmd/rpcdaemon/commands/trace_filtering.go +++ b/cmd/rpcdaemon/commands/trace_filtering.go @@ -3,8 +3,8 @@ package commands import ( "context" "fmt" - "sort" + "github.com/RoaringBitmap/roaring" jsoniter "github.com/json-iterator/go" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/dbutils" @@ -218,97 +218,56 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, str fromAddresses := make(map[common.Address]struct{}, len(req.FromAddress)) toAddresses := make(map[common.Address]struct{}, len(req.ToAddress)) - blocksMap := map[uint32]struct{}{} - var loadFromAddresses = func(addr common.Address) error { - // Load bitmap for address from trace index - b, err := bitmapdb.Get(dbtx, dbutils.CallFromIndex, addr.Bytes(), uint32(fromBlock), uint32(toBlock)) - if err != nil { - return err - } - - // Extract block numbers from bitmap - for _, block := range b.ToArray() { - // Observe the limits - if uint64(block) >= fromBlock && uint64(block) <= toBlock { - blocksMap[block] = struct{}{} - } - } - - return nil - } - var loadToAddresses = func(addr common.Address) error { - // Load bitmap for address from trace index - b, err := bitmapdb.Get(dbtx, dbutils.CallToIndex, addr.Bytes(), uint32(fromBlock), uint32(toBlock)) - if err != nil { - return err - } - - // Extract block numbers from bitmap - for _, block := range b.ToArray() { - // Observe the limits - if uint64(block) >= fromBlock && uint64(block) <= toBlock { - blocksMap[block] = struct{}{} - } - } - - return nil - } - + var allBlocks roaring.Bitmap for _, addr := range req.FromAddress { if addr != nil { - if err := loadFromAddresses(*addr); err != nil { + b, err := bitmapdb.Get(dbtx, dbutils.CallFromIndex, addr.Bytes(), uint32(fromBlock), uint32(toBlock)) + if err != nil { return err } - + allBlocks.Or(b) fromAddresses[*addr] = struct{}{} } } - for _, addr := range req.ToAddress { if addr != nil { - if err := loadToAddresses(*addr); err != nil { + b, err := bitmapdb.Get(dbtx, dbutils.CallToIndex, addr.Bytes(), uint32(fromBlock), uint32(toBlock)) + if err != nil { return err } - + allBlocks.Or(b) toAddresses[*addr] = struct{}{} } } + allBlocks.RemoveRange(0, fromBlock) + allBlocks.RemoveRange(toBlock+1, uint64(0x100000000)) - // Sort blocks - blockSet := make([]int, 0, len(blocksMap)) - for blk := range blocksMap { - blockSet = append(blockSet, int(blk)) - } - sort.Ints(blockSet) - - blocks := make([]*types.Block, 0, len(blocksMap)) - for _, b := range blockSet { - // Extract transactions from block - hash, hashErr := rawdb.ReadCanonicalHash(dbtx, uint64(b)) - if hashErr != nil { - return hashErr - } - - block, _, bErr := rawdb.ReadBlockWithSenders(dbtx, hash, uint64(b)) - if bErr != nil { - return bErr - } - if block == nil { - return fmt.Errorf("could not find block %x %d", hash, uint64(b)) - } - - blocks = append(blocks, block) - } chainConfig, err := api.chainConfig(dbtx) if err != nil { return err } - var json = jsoniter.ConfigCompatibleWithStandardLibrary stream.WriteArrayStart() first := true // Execute all transactions in picked blocks - for _, block := range blocks { + + it := allBlocks.Iterator() + for it.HasNext() { + b := uint64(it.Next()) + // Extract transactions from block + hash, hashErr := rawdb.ReadCanonicalHash(dbtx, b) + if hashErr != nil { + return hashErr + } + + block, _, bErr := rawdb.ReadBlockWithSenders(dbtx, hash, b) + if bErr != nil { + return bErr + } + if block == nil { + return fmt.Errorf("could not find block %x %d", hash, b) + } + blockHash := block.Hash() blockNumber := block.NumberU64() txs := block.Transactions() diff --git a/eth/stagedsync/all_stages.go b/eth/stagedsync/all_stages.go index 7fcfca4f1..71f131482 100644 --- a/eth/stagedsync/all_stages.go +++ b/eth/stagedsync/all_stages.go @@ -75,7 +75,20 @@ func createStageBuilders(blocks []*types.Block, blockNum uint64, checkRoot bool) { ID: stages.Execution, Build: func(world StageParameters) *Stage { - execCfg := StageExecuteBlocksCfg(world.DB.RwKV(), world.storageMode.Receipts, world.BatchSize, world.stateReaderBuilder, world.stateWriterBuilder, world.silkwormExecutionFunc, nil, world.ChainConfig, world.Engine, world.vmConfig, world.TmpDir) + execCfg := StageExecuteBlocksCfg( + world.DB.RwKV(), + world.storageMode.Receipts, + world.storageMode.CallTraces, + world.BatchSize, + world.stateReaderBuilder, + world.stateWriterBuilder, + world.silkwormExecutionFunc, + nil, + world.ChainConfig, + world.Engine, + world.vmConfig, + world.TmpDir, + ) return &Stage{ ID: stages.Execution, Description: "Execute blocks w/o hash checks", diff --git a/eth/stagedsync/stage_call_traces.go b/eth/stagedsync/stage_call_traces.go index b32d6f6aa..6cae9fe6d 100644 --- a/eth/stagedsync/stage_call_traces.go +++ b/eth/stagedsync/stage_call_traces.go @@ -191,12 +191,18 @@ func promoteCallTraces(logPrefix string, tx ethdb.RwTx, startBlock, endBlock uin m.Add(uint32(blockNum)) } } - - if err := flushBitmaps(collectorFrom, froms); err != nil { + if err := finaliseCallTraces(froms, tos, collectorFrom, collectorTo, logPrefix, tx, quit); err != nil { return fmt.Errorf("[%s] %w", logPrefix, err) } + return nil +} + +func finaliseCallTraces(froms, tos map[string]*roaring.Bitmap, collectorFrom, collectorTo *etl.Collector, logPrefix string, tx ethdb.RwTx, quit <-chan struct{}) error { + if err := flushBitmaps(collectorFrom, froms); err != nil { + return err + } if err := flushBitmaps(collectorTo, tos); err != nil { - return fmt.Errorf("[%s] %w", logPrefix, err) + return err } var currentBitmap = roaring.New() @@ -207,14 +213,14 @@ func promoteCallTraces(logPrefix string, tx ethdb.RwTx, startBlock, endBlock uin binary.BigEndian.PutUint32(lastChunkKey[len(k):], ^uint32(0)) lastChunkBytes, err := table.Get(lastChunkKey) if err != nil { - return fmt.Errorf("%s: find last chunk failed: %w", logPrefix, err) + return fmt.Errorf("find last chunk failed: %w", err) } lastChunk := roaring.New() if len(lastChunkBytes) > 0 { _, err = lastChunk.FromBuffer(lastChunkBytes) if err != nil { - return fmt.Errorf("%s: couldn't read last log index chunk: %w, len(lastChunkBytes)=%d", logPrefix, err, len(lastChunkBytes)) + return fmt.Errorf("couldn't read last log index chunk: %w, len(lastChunkBytes)=%d", err, len(lastChunkBytes)) } } @@ -236,11 +242,11 @@ func promoteCallTraces(logPrefix string, tx ethdb.RwTx, startBlock, endBlock uin } if err := collectorFrom.Load(logPrefix, tx, dbutils.CallFromIndex, loaderFunc, etl.TransformArgs{Quit: quit}); err != nil { - return fmt.Errorf("[%s] %w", logPrefix, err) + return err } if err := collectorTo.Load(logPrefix, tx, dbutils.CallToIndex, loaderFunc, etl.TransformArgs{Quit: quit}); err != nil { - return fmt.Errorf("[%s] %w", logPrefix, err) + return err } return nil } diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index 188829872..dc4c05d69 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -8,6 +8,7 @@ import ( "time" "unsafe" + "github.com/RoaringBitmap/roaring" "github.com/c2h5oh/datasize" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/changeset" @@ -47,6 +48,7 @@ type StateWriterBuilder func(db ethdb.Database, changeSetsDB ethdb.RwTx, blockNu type ExecuteBlockCfg struct { db ethdb.RwKV writeReceipts bool + writeCallTraces bool batchSize datasize.ByteSize changeSetHook ChangeSetHook readerBuilder StateReaderBuilder @@ -61,6 +63,7 @@ type ExecuteBlockCfg struct { func StageExecuteBlocksCfg( kv ethdb.RwKV, WriteReceipts bool, + WriteCallTraces bool, BatchSize datasize.ByteSize, ReaderBuilder StateReaderBuilder, WriterBuilder StateWriterBuilder, @@ -74,6 +77,7 @@ func StageExecuteBlocksCfg( return ExecuteBlockCfg{ db: kv, writeReceipts: WriteReceipts, + writeCallTraces: WriteCallTraces, batchSize: BatchSize, changeSetHook: ChangeSetHook, readerBuilder: ReaderBuilder, @@ -95,7 +99,8 @@ func readBlock(blockNum uint64, tx ethdb.Tx) (*types.Block, error) { return b, err } -func executeBlockWithGo(block *types.Block, tx ethdb.RwTx, batch ethdb.Database, params ExecuteBlockCfg) error { +func executeBlockWithGo(block *types.Block, tx ethdb.RwTx, batch ethdb.Database, params ExecuteBlockCfg, + froms, tos map[string]*roaring.Bitmap, collectorFrom, collectorTo *etl.Collector, bitmapCounter *int) error { blockNum := block.NumberU64() var stateReader state.StateReader var stateWriter state.WriterWithChangeSets @@ -114,6 +119,12 @@ func executeBlockWithGo(block *types.Block, tx ethdb.RwTx, batch ethdb.Database, // where the magic happens getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) } + var callTracer *CallTracer + if params.writeCallTraces { + callTracer = NewCallTracer() + params.vmConfig.Debug = true + params.vmConfig.Tracer = callTracer + } receipts, err := core.ExecuteBlockEphemerally(params.chainConfig, params.vmConfig, getHeader, params.engine, block, stateReader, stateWriter) if err != nil { return err @@ -131,6 +142,45 @@ func executeBlockWithGo(block *types.Block, tx ethdb.RwTx, batch ethdb.Database, } } + if params.writeCallTraces { + callTracer.tos[block.Coinbase()] = struct{}{} + for _, uncle := range block.Uncles() { + callTracer.tos[uncle.Coinbase] = struct{}{} + } + + for addr := range callTracer.froms { + m, ok := froms[string(addr[:])] + if !ok { + m = roaring.New() + a := addr // To copy addr + froms[string(a[:])] = m + } + m.Add(uint32(blockNum)) + *bitmapCounter++ + } + for addr := range callTracer.tos { + m, ok := tos[string(addr[:])] + if !ok { + m = roaring.New() + a := addr // To copy addr + tos[string(a[:])] = m + } + m.Add(uint32(blockNum)) + *bitmapCounter++ + } + + if *bitmapCounter > 1000000 { + if err := flushBitmaps(collectorFrom, froms); err != nil { + return err + } + if err := flushBitmaps(collectorTo, tos); err != nil { + return err + } + log.Info("Flushed bitmaps to collectors") + *bitmapCounter = 0 + } + } + return nil } @@ -160,6 +210,16 @@ func SpawnExecuteBlocksStage(s *StageState, tx ethdb.RwTx, toBlock uint64, quit logPrefix := s.state.LogPrefix() log.Info(fmt.Sprintf("[%s] Blocks execution", logPrefix), "from", s.BlockNumber, "to", to) + var froms, tos map[string]*roaring.Bitmap + var collectorFrom, collectorTo *etl.Collector + var bitmapCounter int + if cfg.writeCallTraces { + froms = map[string]*roaring.Bitmap{} + tos = map[string]*roaring.Bitmap{} + collectorFrom = etl.NewCollector(cfg.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize)) + collectorTo = etl.NewCollector(cfg.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize)) + } + useSilkworm := cfg.silkwormExecutionFunc != nil if useSilkworm && cfg.changeSetHook != nil { panic("ChangeSetHook is not supported with Silkworm") @@ -197,7 +257,7 @@ func SpawnExecuteBlocksStage(s *StageState, tx ethdb.RwTx, toBlock uint64, quit log.Error(fmt.Sprintf("[%s] Empty block", logPrefix), "blocknum", blockNum) break } - if err = executeBlockWithGo(block, tx, batch, cfg); err != nil { + if err = executeBlockWithGo(block, tx, batch, cfg, froms, tos, collectorFrom, collectorTo, &bitmapCounter); err != nil { return err } } @@ -251,6 +311,13 @@ func SpawnExecuteBlocksStage(s *StageState, tx ethdb.RwTx, toBlock uint64, quit return err } } + + if cfg.writeCallTraces { + if err := finaliseCallTraces(froms, tos, collectorFrom, collectorTo, logPrefix, tx, quit); err != nil { + return fmt.Errorf("[%s] %w", logPrefix, err) + } + } + log.Info(fmt.Sprintf("[%s] Completed on", logPrefix), "block", stageProgress) s.Done() return nil @@ -378,6 +445,12 @@ func unwindExecutionStage(u *UnwindState, s *StageState, tx ethdb.RwTx, quit <-c } } + if cfg.writeCallTraces { + if err := unwindCallTraces(logPrefix, tx, s.BlockNumber, u.UnwindPoint, quit, CallTracesCfg{engine: cfg.engine, chainConfig: cfg.chainConfig}); err != nil { + return fmt.Errorf("%s: unwinding call traces: %v", logPrefix, err) + } + } + return nil } diff --git a/eth/stagedsync/stagebuilder.go b/eth/stagedsync/stagebuilder.go index e290af5d1..5e4e2cc37 100644 --- a/eth/stagedsync/stagebuilder.go +++ b/eth/stagedsync/stagebuilder.go @@ -199,7 +199,20 @@ func DefaultStages() StageBuilders { { ID: stages.Execution, Build: func(world StageParameters) *Stage { - execCfg := StageExecuteBlocksCfg(world.DB.RwKV(), world.storageMode.Receipts, world.BatchSize, world.stateReaderBuilder, world.stateWriterBuilder, world.silkwormExecutionFunc, nil, world.ChainConfig, world.Engine, world.vmConfig, world.TmpDir) + execCfg := StageExecuteBlocksCfg( + world.DB.RwKV(), + world.storageMode.Receipts, + world.storageMode.CallTraces, + world.BatchSize, + world.stateReaderBuilder, + world.stateWriterBuilder, + world.silkwormExecutionFunc, + nil, + world.ChainConfig, + world.Engine, + world.vmConfig, + world.TmpDir, + ) return &Stage{ ID: stages.Execution, Description: "Execute blocks w/o hash checks",