From 1169cc59a58bb1a811f4b170fd0f2fe8d409c96e Mon Sep 17 00:00:00 2001 From: Dalibor Date: Fri, 24 Jan 2025 11:32:47 +0100 Subject: [PATCH] Live tracing experimental MVP support for arbitrum nitro --- .gitmodules | 7 +++-- arbos/block_processor.go | 45 +++++++++++++++++++++++++-- cmd/nitro/init.go | 29 +++++++++++++++-- cmd/nitro/nitro.go | 21 ++++++++++++- cmd/replay/main.go | 12 ++++++- execution/gethexec/block_recorder.go | 1 + execution/gethexec/blockchain.go | 20 ++++++++++-- execution/gethexec/executionengine.go | 22 ++++++++----- go-ethereum | 2 +- system_tests/state_fuzz_test.go | 10 +++++- 10 files changed, 145 insertions(+), 24 deletions(-) diff --git a/.gitmodules b/.gitmodules index 24df007a79..3ac0e33e21 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "go-ethereum"] - path = go-ethereum - url = https://github.com/OffchainLabs/go-ethereum.git [submodule "fastcache"] path = fastcache url = https://github.com/OffchainLabs/fastcache.git @@ -38,3 +35,7 @@ [submodule "safe-smart-account"] path = safe-smart-account url = https://github.com/safe-global/safe-smart-account.git +[submodule "go-ethereum"] + path = go-ethereum + url = https://github.com/Tenderly/arb-go-ethereum.git + branch = live-tracing diff --git a/arbos/block_processor.go b/arbos/block_processor.go index a06034f905..2bf3ec4b65 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/tracing" "math" "math/big" @@ -147,6 +148,7 @@ func ProduceBlock( chainConfig *params.ChainConfig, isMsgForPrefetch bool, runMode core.MessageRunMode, + blockChain *core.BlockChain, ) (*types.Block, types.Receipts, error) { txes, err := ParseL2Transactions(message, chainConfig.ChainID) if err != nil { @@ -156,7 +158,17 @@ func ProduceBlock( hooks := NoopSequencingHooks() return ProduceBlockAdvanced( - message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, runMode, + message.Header, + txes, + delayedMessagesRead, + lastBlockHeader, + statedb, + chainContext, + chainConfig, + hooks, + isMsgForPrefetch, + runMode, + blockChain, ) } @@ -172,7 +184,21 @@ func ProduceBlockAdvanced( sequencingHooks *SequencingHooks, isMsgForPrefetch bool, runMode core.MessageRunMode, -) (*types.Block, types.Receipts, error) { + blockChain *core.BlockChain, +) (outBlock *types.Block, outReceipt types.Receipts, outError error) { + // IMPORTANT: + // func to get the VMConfig from the blockChain, or a default one if it's not available + // this is a way how we propagate Tenderly tracer to the VMConfig instead of vm.Config{} + getVMConfig := func() vm.Config { + if blockChain != nil && blockChain.GetVMConfig() != nil { + return *blockChain.GetVMConfig() + } + return vm.Config{} + } + + defer func() { + getVMConfig().Tracer.OnBlockEnd(outError) + }() arbState, err := arbosState.OpenSystemArbosState(statedb, nil, true) if err != nil { @@ -192,6 +218,16 @@ func ProduceBlockAdvanced( } header := createNewHeader(lastBlockHeader, l1Info, arbState, chainConfig) + + getVMConfig().Tracer.OnBlockStart( + tracing.BlockEvent{ + Block: types.NewBlock(header, &types.Body{Transactions: nil}, nil, trie.NewStackTrie(nil)), + TD: nil, + Finalized: nil, + Safe: nil, + }, + ) + signer := types.MakeSigner(chainConfig, header.Number, header.Time) // Note: blockGasLeft will diverge from the actual gas left during execution in the event of invalid txs, // but it's only used as block-local representation limiting the amount of work done in a block. @@ -312,6 +348,9 @@ func ProduceBlockAdvanced( statedb.SetTxContext(tx.Hash(), len(receipts)) // the number of successful state transitions gasPool := gethGas + + statedb.SetLogger(getVMConfig().Tracer) + receipt, result, err := core.ApplyTransactionWithResultFilter( chainConfig, chainContext, @@ -321,7 +360,7 @@ func ProduceBlockAdvanced( header, tx, &header.GasUsed, - vm.Config{}, + getVMConfig(), runMode, func(result *core.ExecutionResult) error { return hooks.PostTxFilter(header, statedb, arbState, tx, sender, dataGas, result) diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 93c51a0040..3bc8f71bbe 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -541,7 +541,18 @@ func rebuildLocalWasm(ctx context.Context, config *gethexec.Config, l2BlockChain return chainDb, l2BlockChain, nil } -func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, targetConfig *gethexec.StylusTargetConfig, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { +func openInitializeChainDb( + ctx context.Context, + stack *node.Node, + config *NodeConfig, + chainId *big.Int, + cacheConfig *core.CacheConfig, + targetConfig *gethexec.StylusTargetConfig, + persistentConfig *conf.PersistentConfig, + l1Client *ethclient.Client, + rollupAddrs chaininfo.RollupAddresses, + tracingConfig json.RawMessage, +) (ethdb.Database, *core.BlockChain, error) { if !config.Init.Force { if readOnlyDb, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", 0, 0, config.Persistent.Ancient, "l2chaindata/", true, persistentConfig.Pebble.ExtraOptions("l2chaindata")); err == nil { if chainConfig := gethexec.TryReadStoredChainConfig(readOnlyDb); chainConfig != nil { @@ -575,7 +586,13 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err != nil { return chainDb, nil, fmt.Errorf("error pruning: %w", err) } - l2BlockChain, err := gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, config.Execution.TxLookupLimit) + l2BlockChain, err := gethexec.GetBlockChain( + chainDb, + cacheConfig, + chainConfig, + config.Execution.TxLookupLimit, + tracingConfig, + ) if err != nil { return chainDb, nil, err } @@ -726,7 +743,13 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if chainConfig == nil { return chainDb, nil, errors.New("no --init.* mode supplied and chain data not in expected directory") } - l2BlockChain, err = gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, config.Execution.TxLookupLimit) + l2BlockChain, err = gethexec.GetBlockChain( + chainDb, + cacheConfig, + chainConfig, + config.Execution.TxLookupLimit, + tracingConfig, + ) if err != nil { return chainDb, nil, err } diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index e4e1b79353..8ed517151a 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -7,6 +7,7 @@ import ( "context" "crypto/ecdsa" "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -170,6 +171,13 @@ func mainImpl() int { defer cancelFunc() args := os.Args[1:] + + //var tracingConfig json.RawMessage + // TODO: We need a better way to pass this config to the tracer + // this is just a temporary solution and + // it is a demonstration of how to pass a config to live tracer as a json.RawMessage via tracingConfig + tracingConfig := json.RawMessage(`{"local_store": "/livetracing/output", "ttl": 14, "network_id": "42161", "enable_code_tracing":false }`) + nodeConfig, l2DevWallet, err := ParseNode(ctx, args) if err != nil { confighelpers.PrintErrorAndExit(err, printSampleUsage) @@ -437,7 +445,18 @@ func mainImpl() int { } } - chainDb, l2BlockChain, err := openInitializeChainDb(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), &nodeConfig.Execution.StylusTarget, &nodeConfig.Persistent, l1Client, rollupAddrs) + chainDb, l2BlockChain, err := openInitializeChainDb( + ctx, + stack, + nodeConfig, + new(big.Int).SetUint64(nodeConfig.Chain.ID), + gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), + &nodeConfig.Execution.StylusTarget, + &nodeConfig.Persistent, + l1Client, + rollupAddrs, + tracingConfig, + ) if l2BlockChain != nil { deferFuncs = append(deferFuncs, func() { l2BlockChain.Stop() }) } diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 661040ea10..e72914c5c1 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -292,7 +292,17 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) chainContext := WavmChainContext{} - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, core.MessageReplayMode) + newBlock, _, err = arbos.ProduceBlock( + message.Message, + message.DelayedMessagesRead, + lastBlockHeader, + statedb, + chainContext, + chainConfig, + false, + core.MessageReplayMode, + nil, // todo: tenderly + ) if err != nil { panic(err) } diff --git a/execution/gethexec/block_recorder.go b/execution/gethexec/block_recorder.go index 2e3d51fec9..9794cc2270 100644 --- a/execution/gethexec/block_recorder.go +++ b/execution/gethexec/block_recorder.go @@ -160,6 +160,7 @@ func (r *BlockRecorder) RecordBlockCreation( chainConfig, false, core.MessageReplayMode, + nil, // todo: tenderly ) if err != nil { return nil, err diff --git a/execution/gethexec/blockchain.go b/execution/gethexec/blockchain.go index 53b494a3c2..4f3c1ce7a4 100644 --- a/execution/gethexec/blockchain.go +++ b/execution/gethexec/blockchain.go @@ -1,8 +1,10 @@ package gethexec import ( + "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/eth/tracers/live" "math/big" "time" @@ -202,13 +204,25 @@ func WriteOrTestChainConfig(chainDb ethdb.Database, config *params.ChainConfig) return nil } -func GetBlockChain(chainDb ethdb.Database, cacheConfig *core.CacheConfig, chainConfig *params.ChainConfig, txLookupLimit uint64) (*core.BlockChain, error) { +func GetBlockChain( + chainDb ethdb.Database, + cacheConfig *core.CacheConfig, + chainConfig *params.ChainConfig, + txLookupLimit uint64, + tracingConfig json.RawMessage, +) (*core.BlockChain, error) { engine := arbos.Engine{ IsSequencer: true, } + tenderlySimpleTracerHooks, err := live.NewTenderlySimpleTracerHooks(tracingConfig) + if err != nil { + return nil, err + } + vmConfig := vm.Config{ EnablePreimageRecording: false, + Tracer: tenderlySimpleTracerHooks, } return core.NewBlockChain(chainDb, cacheConfig, chainConfig, nil, nil, engine, vmConfig, shouldPreserveFalse, &txLookupLimit) @@ -220,7 +234,7 @@ func WriteOrTestBlockChain(chainDb ethdb.Database, cacheConfig *core.CacheConfig // When using path scheme, and the stored state trie is not empty, // WriteOrTestGenBlock is not able to recover EmptyRootHash state trie node. // In that case Nitro doesn't test genblock, but just returns the BlockChain. - return GetBlockChain(chainDb, cacheConfig, chainConfig, txLookupLimit) + return GetBlockChain(chainDb, cacheConfig, chainConfig, txLookupLimit, nil) } err := WriteOrTestGenblock(chainDb, cacheConfig, initData, chainConfig, initMessage, accountsPerSync) @@ -231,7 +245,7 @@ func WriteOrTestBlockChain(chainDb ethdb.Database, cacheConfig *core.CacheConfig if err != nil { return nil, err } - return GetBlockChain(chainDb, cacheConfig, chainConfig, txLookupLimit) + return GetBlockChain(chainDb, cacheConfig, chainConfig, txLookupLimit, nil) } // Don't preserve reorg'd out blocks diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index e606027419..80f4e6cedd 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -525,6 +525,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. hooks, false, core.MessageCommitMode, + nil, ) if err != nil { return nil, err @@ -696,6 +697,7 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith s.bc.Config(), isMsgForPrefetch, runMode, + s.bc, ) return block, statedb, receipts, err @@ -873,14 +875,18 @@ func (s *ExecutionEngine) digestMessageWithBlockMutex(num arbutil.MessageIndex, } startTime := time.Now() - if s.prefetchBlock && msgForPrefetch != nil { - go func() { - _, _, _, err := s.createBlockFromNextMessage(msgForPrefetch, true) - if err != nil { - return - } - }() - } + + // TODO: IMPORTANT !!! + // This will cause a tracer to be called multiple times for the same block concurrently + // Removed for now, but this should be properly addressed + //if s.prefetchBlock && msgForPrefetch != nil { + // go func() { + // _, _, _, err := s.createBlockFromNextMessage(msgForPrefetch, true) + // if err != nil { + // return + // } + // }() + //} block, statedb, receipts, err := s.createBlockFromNextMessage(msg, false) if err != nil { diff --git a/go-ethereum b/go-ethereum index 779b669ac0..39d5bfc57e 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 779b669ac0d0020099a67a1c39fbaf66b901c1a5 +Subproject commit 39d5bfc57ed17ecb82c46a1a58406889deb47d2f diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 8388e8417c..8f2fdbc14a 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -68,7 +68,15 @@ func BuildBlock( } block, _, err := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, runMode, + l1Message, + delayedMessagesRead, + lastBlockHeader, + statedb, + chainContext, + chainConfig, + false, + runMode, + nil, ) return block, err }