From 910666f7bd2db9575751ead1f6a9136da34e47b5 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 26 Sep 2024 14:37:51 +0200 Subject: [PATCH 01/20] add MessageRunMode to ProduceBlock parameters --- arbos/block_processor.go | 5 ++++- cmd/replay/main.go | 3 ++- execution/gethexec/block_recorder.go | 2 ++ execution/gethexec/executionengine.go | 6 ++++++ system_tests/state_fuzz_test.go | 7 ++++--- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index b180405c43..19fc36b351 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -144,6 +144,7 @@ func ProduceBlock( chainContext core.ChainContext, chainConfig *params.ChainConfig, isMsgForPrefetch bool, + runMode core.MessageRunMode, ) (*types.Block, types.Receipts, error) { txes, err := ParseL2Transactions(message, chainConfig.ChainID) if err != nil { @@ -153,7 +154,7 @@ func ProduceBlock( hooks := NoopSequencingHooks() return ProduceBlockAdvanced( - message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, + message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, runMode, ) } @@ -168,6 +169,7 @@ func ProduceBlockAdvanced( chainConfig *params.ChainConfig, sequencingHooks *SequencingHooks, isMsgForPrefetch bool, + runMode core.MessageRunMode, ) (*types.Block, types.Receipts, error) { state, err := arbosState.OpenSystemArbosState(statedb, nil, true) @@ -318,6 +320,7 @@ func ProduceBlockAdvanced( tx, &header.GasUsed, vm.Config{}, + runMode, func(result *core.ExecutionResult) error { return hooks.PostTxFilter(header, state, tx, sender, dataGas, result) }, diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 0fe56eb4c9..d10d57a9c7 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -291,7 +292,7 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) chainContext := WavmChainContext{} - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false) + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, core.MessageReplayMode) // TODO verify runMode if err != nil { panic(err) } diff --git a/execution/gethexec/block_recorder.go b/execution/gethexec/block_recorder.go index a31b6b3736..a3af7876a8 100644 --- a/execution/gethexec/block_recorder.go +++ b/execution/gethexec/block_recorder.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -154,6 +155,7 @@ func (r *BlockRecorder) RecordBlockCreation( chaincontext, chainConfig, false, + core.MessageReplayMode, ) if err != nil { return nil, err diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 8d6484e3c9..d21522d2d4 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -505,6 +505,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. s.bc.Config(), hooks, false, + core.MessageCommitMode, ) if err != nil { return nil, err @@ -661,6 +662,10 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb.StartPrefetcher("TransactionStreamer") defer statedb.StopPrefetcher() + runMode := core.MessageCommitMode + if isMsgForPrefetch { + runMode = core.MessageReplayMode + } block, receipts, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, @@ -669,6 +674,7 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith s.bc, s.bc.Config(), isMsgForPrefetch, + runMode, ) return block, statedb, receipts, err diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 24140e480d..8fdfa3a098 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -38,6 +38,7 @@ func BuildBlock( chainConfig *params.ChainConfig, inbox arbstate.InboxBackend, seqBatch []byte, + runMode core.MessageRunMode, // TODO do we need to fuzz runMode? ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { @@ -63,7 +64,7 @@ func BuildBlock( } block, _, err := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, runMode, ) return block, err } @@ -127,7 +128,7 @@ func (c noopChainContext) GetHeader(common.Hash, uint64) *types.Header { } func FuzzStateTransition(f *testing.F) { - f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte) { + f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, runMode uint8) { if len(seqMsg) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { return } @@ -201,7 +202,7 @@ func FuzzStateTransition(f *testing.F) { positionWithinMessage: 0, delayedMessages: delayedMessages, } - _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch) + _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch, core.MessageRunMode(runMode)) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. From 05133258c92380005e1492304fbf08e137ef1c5d Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 26 Sep 2024 17:09:49 +0200 Subject: [PATCH 02/20] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 17cd001675..0c3f6eba21 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 17cd00167543a5a2b0b083e32820051100154c2f +Subproject commit 0c3f6eba21cbe0196b298dfbd3fa7d51dffd627e From ed680537f60ce8af3b6caa660b41698e77f2e007 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 26 Sep 2024 18:16:51 +0200 Subject: [PATCH 03/20] remove outdated todo comments --- cmd/replay/main.go | 2 +- system_tests/state_fuzz_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/replay/main.go b/cmd/replay/main.go index d10d57a9c7..661040ea10 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -292,7 +292,7 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) chainContext := WavmChainContext{} - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, core.MessageReplayMode) // TODO verify runMode + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, core.MessageReplayMode) if err != nil { panic(err) } diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 8fdfa3a098..c0477060ed 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -38,7 +38,7 @@ func BuildBlock( chainConfig *params.ChainConfig, inbox arbstate.InboxBackend, seqBatch []byte, - runMode core.MessageRunMode, // TODO do we need to fuzz runMode? + runMode core.MessageRunMode, ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { From 39e94812606ba64d8d719be9729c955a0907d4d7 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 27 Sep 2024 00:51:36 +0200 Subject: [PATCH 04/20] fuzz state transition: skip malformed batch posting report --- system_tests/state_fuzz_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index c0477060ed..2287870cb5 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -60,7 +60,8 @@ func BuildBlock( } err = l1Message.FillInBatchGasCost(batchFetcher) if err != nil { - return nil, err + // skip malformed batch posting report + return nil, nil } block, _, err := arbos.ProduceBlock( From e4d4b971d3ae60b124b220f3e8a6f745faed29cc Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 27 Sep 2024 00:55:45 +0200 Subject: [PATCH 05/20] fuzz state transition: test only existing message run modes --- system_tests/state_fuzz_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 2287870cb5..d722aa4212 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -129,7 +129,7 @@ func (c noopChainContext) GetHeader(common.Hash, uint64) *types.Header { } func FuzzStateTransition(f *testing.F) { - f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, runMode uint8) { + f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, runModeSeed uint8) { if len(seqMsg) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { return } @@ -203,7 +203,9 @@ func FuzzStateTransition(f *testing.F) { positionWithinMessage: 0, delayedMessages: delayedMessages, } - _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch, core.MessageRunMode(runMode)) + numberOfMessageRunModes := uint8(core.MessageReplayMode) + 1 // TODO update number of run modes when new mode is added + runMode := core.MessageRunMode(runModeSeed % numberOfMessageRunModes) + _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch, runMode) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. From 760081d9d0a34cc9acb6a509a953f689a47f36b3 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 27 Sep 2024 01:06:23 +0200 Subject: [PATCH 06/20] make lint happy --- system_tests/state_fuzz_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index d722aa4212..c8312350e6 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -61,6 +61,7 @@ func BuildBlock( err = l1Message.FillInBatchGasCost(batchFetcher) if err != nil { // skip malformed batch posting report + // nolint:nilerr return nil, nil } From 14d57e1da2482b8fb145eb94d6e806876cf63a58 Mon Sep 17 00:00:00 2001 From: Diego Ximenes Date: Mon, 30 Sep 2024 11:08:38 -0300 Subject: [PATCH 07/20] DisableStylusCacheMetricsCollection flag --- execution/gethexec/blockchain.go | 30 ++++++++++++----------- execution/gethexec/executionengine.go | 34 +++++++++++++++++++-------- execution/gethexec/node.go | 3 +++ system_tests/common_test.go | 27 +++++++++++---------- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/execution/gethexec/blockchain.go b/execution/gethexec/blockchain.go index 9b0c1a6f2f..fda8f49093 100644 --- a/execution/gethexec/blockchain.go +++ b/execution/gethexec/blockchain.go @@ -26,20 +26,21 @@ import ( ) type CachingConfig struct { - Archive bool `koanf:"archive"` - BlockCount uint64 `koanf:"block-count"` - BlockAge time.Duration `koanf:"block-age"` - TrieTimeLimit time.Duration `koanf:"trie-time-limit"` - TrieDirtyCache int `koanf:"trie-dirty-cache"` - TrieCleanCache int `koanf:"trie-clean-cache"` - SnapshotCache int `koanf:"snapshot-cache"` - DatabaseCache int `koanf:"database-cache"` - SnapshotRestoreGasLimit uint64 `koanf:"snapshot-restore-gas-limit"` - MaxNumberOfBlocksToSkipStateSaving uint32 `koanf:"max-number-of-blocks-to-skip-state-saving"` - MaxAmountOfGasToSkipStateSaving uint64 `koanf:"max-amount-of-gas-to-skip-state-saving"` - StylusLRUCacheCapacity uint32 `koanf:"stylus-lru-cache-capacity"` - StateScheme string `koanf:"state-scheme"` - StateHistory uint64 `koanf:"state-history"` + Archive bool `koanf:"archive"` + BlockCount uint64 `koanf:"block-count"` + BlockAge time.Duration `koanf:"block-age"` + TrieTimeLimit time.Duration `koanf:"trie-time-limit"` + TrieDirtyCache int `koanf:"trie-dirty-cache"` + TrieCleanCache int `koanf:"trie-clean-cache"` + SnapshotCache int `koanf:"snapshot-cache"` + DatabaseCache int `koanf:"database-cache"` + SnapshotRestoreGasLimit uint64 `koanf:"snapshot-restore-gas-limit"` + MaxNumberOfBlocksToSkipStateSaving uint32 `koanf:"max-number-of-blocks-to-skip-state-saving"` + MaxAmountOfGasToSkipStateSaving uint64 `koanf:"max-amount-of-gas-to-skip-state-saving"` + StylusLRUCacheCapacity uint32 `koanf:"stylus-lru-cache-capacity"` + DisableStylusCacheMetricsCollection bool `koanf:"disable-stylus-cache-metrics-collection"` + StateScheme string `koanf:"state-scheme"` + StateHistory uint64 `koanf:"state-history"` } func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -55,6 +56,7 @@ func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { f.Uint32(prefix+".max-number-of-blocks-to-skip-state-saving", DefaultCachingConfig.MaxNumberOfBlocksToSkipStateSaving, "maximum number of blocks to skip state saving to persistent storage (archive node only) -- warning: this option seems to cause issues") f.Uint64(prefix+".max-amount-of-gas-to-skip-state-saving", DefaultCachingConfig.MaxAmountOfGasToSkipStateSaving, "maximum amount of gas in blocks to skip saving state to Persistent storage (archive node only) -- warning: this option seems to cause issues") f.Uint32(prefix+".stylus-lru-cache-capacity", DefaultCachingConfig.StylusLRUCacheCapacity, "capacity, in megabytes, of the LRU cache that keeps initialized stylus programs") + f.Bool(prefix+".disable-stylus-cache-metrics-collection", DefaultCachingConfig.DisableStylusCacheMetricsCollection, "disable metrics collection for the stylus cache") f.String(prefix+".state-scheme", DefaultCachingConfig.StateScheme, "scheme to use for state trie storage (hash, path)") f.Uint64(prefix+".state-history", DefaultCachingConfig.StateHistory, "number of recent blocks to retain state history for (path state-scheme only)") } diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index a0f3a2f59a..b36340757a 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -87,6 +87,8 @@ type ExecutionEngine struct { reorgSequencing bool + disableStylusCacheMetricsCollection bool + prefetchBlock bool cachedL1PriceData *L1PriceData @@ -212,6 +214,16 @@ func (s *ExecutionEngine) EnableReorgSequencing() { s.reorgSequencing = true } +func (s *ExecutionEngine) DisableStylusCacheMetricsCollection() { + if s.Started() { + panic("trying to disable stylus cache metrics collection after start") + } + if s.disableStylusCacheMetricsCollection { + panic("trying to disable stylus cache metrics collection when already set") + } + s.disableStylusCacheMetricsCollection = true +} + func (s *ExecutionEngine) EnablePrefetchBlock() { if s.Started() { panic("trying to enable prefetch block after start") @@ -963,15 +975,17 @@ func (s *ExecutionEngine) Start(ctx_in context.Context) { } } }) - // periodically update stylus lru cache metrics - s.LaunchThread(func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case <-time.After(time.Minute): - programs.GetWasmLruCacheMetrics() + if !s.disableStylusCacheMetricsCollection { + // periodically update stylus lru cache metrics + s.LaunchThread(func(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Minute): + programs.GetWasmLruCacheMetrics() + } } - } - }) + }) + } } diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 5a1efc6d08..1b8b756502 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -187,6 +187,9 @@ func CreateExecutionNode( if config.EnablePrefetchBlock { execEngine.EnablePrefetchBlock() } + if config.Caching.DisableStylusCacheMetricsCollection { + execEngine.DisableStylusCacheMetricsCollection() + } if err != nil { return nil, err } diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 5902a670ba..6a4e551906 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -155,19 +155,20 @@ func (tc *TestClient) EnsureTxSucceededWithTimeout(transaction *types.Transactio } var TestCachingConfig = gethexec.CachingConfig{ - Archive: false, - BlockCount: 128, - BlockAge: 30 * time.Minute, - TrieTimeLimit: time.Hour, - TrieDirtyCache: 1024, - TrieCleanCache: 600, - SnapshotCache: 400, - DatabaseCache: 2048, - SnapshotRestoreGasLimit: 300_000_000_000, - MaxNumberOfBlocksToSkipStateSaving: 0, - MaxAmountOfGasToSkipStateSaving: 0, - StylusLRUCacheCapacity: 0, - StateScheme: env.GetTestStateScheme(), + Archive: false, + BlockCount: 128, + BlockAge: 30 * time.Minute, + TrieTimeLimit: time.Hour, + TrieDirtyCache: 1024, + TrieCleanCache: 600, + SnapshotCache: 400, + DatabaseCache: 2048, + SnapshotRestoreGasLimit: 300_000_000_000, + MaxNumberOfBlocksToSkipStateSaving: 0, + MaxAmountOfGasToSkipStateSaving: 0, + StylusLRUCacheCapacity: 0, + DisableStylusCacheMetricsCollection: true, + StateScheme: env.GetTestStateScheme(), } var DefaultTestForwarderConfig = gethexec.ForwarderConfig{ From 5e8c4a2535733f5cea6b50b57594b684e01464f6 Mon Sep 17 00:00:00 2001 From: Diego Ximenes Date: Mon, 30 Sep 2024 13:44:24 -0300 Subject: [PATCH 08/20] Stylus long term cache metrics --- arbitrator/stylus/src/cache.rs | 60 +++++++++++++++++++++++---- arbitrator/stylus/src/lib.rs | 16 +++---- arbos/programs/native.go | 59 ++++++++++++++++++++------ execution/gethexec/executionengine.go | 4 +- system_tests/program_test.go | 26 ++++++------ 5 files changed, 121 insertions(+), 44 deletions(-) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index c1fdaaccee..827e2beaa3 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -29,8 +29,16 @@ pub struct LruCounters { pub does_not_fit: u32, } +pub struct LongTermCounters { + pub hits: u32, + pub misses: u32, +} + pub struct InitCache { long_term: HashMap, + long_term_size_bytes: usize, + long_term_counters: LongTermCounters, + lru: CLruCache, lru_counters: LruCounters, } @@ -91,6 +99,20 @@ pub struct LruCacheMetrics { pub does_not_fit: u32, } +#[repr(C)] +pub struct LongTermCacheMetrics { + pub size_bytes: u64, + pub count: u32, + pub hits: u32, + pub misses: u32, +} + +#[repr(C)] +pub struct CacheMetrics { + pub lru: LruCacheMetrics, + pub long_term: LongTermCacheMetrics, +} + pub fn deserialize_module( module: &[u8], version: u16, @@ -117,6 +139,9 @@ impl InitCache { fn new(size_bytes: usize) -> Self { Self { long_term: HashMap::new(), + long_term_size_bytes: 0, + long_term_counters: LongTermCounters { hits: 0, misses: 0 }, + lru: CLruCache::with_config( CLruCacheConfig::new(NonZeroUsize::new(size_bytes).unwrap()) .with_scale(CustomWeightScale), @@ -142,8 +167,11 @@ impl InitCache { // See if the item is in the long term cache if let Some(item) = cache.long_term.get(&key) { - return Some(item.data()); + let data = item.data(); + cache.long_term_counters.hits += 1; + return Some(data); } + cache.long_term_counters.misses += 1; // See if the item is in the LRU cache, promoting if so if let Some(item) = cache.lru.get(&key) { @@ -174,6 +202,7 @@ impl InitCache { if let Some(item) = cache.lru.peek(&key).cloned() { if long_term_tag == Self::ARBOS_TAG { cache.long_term.insert(key, item.clone()); + cache.long_term_size_bytes += item.entry_size_estimate_bytes; } else { // only calls get to move the key to the head of the LRU list cache.lru.get(&key); @@ -195,6 +224,7 @@ impl InitCache { }; } else { cache.long_term.insert(key, item); + cache.long_term_size_bytes += entry_size_estimate_bytes; } Ok(data) } @@ -207,6 +237,7 @@ impl InitCache { let key = CacheKey::new(module_hash, version, debug); let mut cache = cache!(); if let Some(item) = cache.long_term.remove(&key) { + cache.long_term_size_bytes -= item.entry_size_estimate_bytes; if cache.lru.put_with_weight(key, item).is_err() { eprintln!("{}", Self::DOES_NOT_FIT_MSG); } @@ -225,23 +256,32 @@ impl InitCache { eprintln!("{}", Self::DOES_NOT_FIT_MSG); } } + cache.long_term_size_bytes = 0; } - pub fn get_lru_metrics() -> LruCacheMetrics { + pub fn get_metrics() -> CacheMetrics { let mut cache = cache!(); - let count = cache.lru.len(); - let metrics = LruCacheMetrics { - // add 1 to each entry to account that we subtracted 1 in the weight calculation - size_bytes: (cache.lru.weight() + count).try_into().unwrap(), + let lru_count = cache.lru.len(); + let lru_metrics = LruCacheMetrics { + // adds 1 to each entry to account that we subtracted 1 in the weight calculation + size_bytes: (cache.lru.weight() + lru_count).try_into().unwrap(), - count: count.try_into().unwrap(), + count: lru_count.try_into().unwrap(), hits: cache.lru_counters.hits, misses: cache.lru_counters.misses, does_not_fit: cache.lru_counters.does_not_fit, }; + let long_term_metrics = LongTermCacheMetrics { + size_bytes: cache.long_term_size_bytes.try_into().unwrap(), + count: cache.long_term.len().try_into().unwrap(), + + hits: cache.long_term_counters.hits, + misses: cache.long_term_counters.misses, + }; + // Empty counters. // go side, which is the only consumer of this function besides tests, // will read those counters and increment its own prometheus counters with them. @@ -250,8 +290,12 @@ impl InitCache { misses: 0, does_not_fit: 0, }; + cache.long_term_counters = LongTermCounters { hits: 0, misses: 0 }; - metrics + CacheMetrics { + lru: lru_metrics, + long_term: long_term_metrics, + } } // only used for testing diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index abea428167..c16f3d7598 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -11,7 +11,7 @@ use arbutil::{ format::DebugBytes, Bytes32, }; -use cache::{deserialize_module, InitCache, LruCacheMetrics}; +use cache::{deserialize_module, InitCache, CacheMetrics}; use evm_api::NativeRequestHandler; use eyre::ErrReport; use native::NativeInstance; @@ -364,10 +364,10 @@ pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) { } } -/// Gets lru cache metrics. +/// Gets cache metrics. #[no_mangle] -pub extern "C" fn stylus_get_lru_cache_metrics() -> LruCacheMetrics { - InitCache::get_lru_metrics() +pub extern "C" fn stylus_get_cache_metrics() -> CacheMetrics { + InitCache::get_metrics() } /// Clears lru cache. @@ -377,18 +377,18 @@ pub extern "C" fn stylus_clear_lru_cache() { InitCache::clear_lru_cache() } -/// Gets lru entry size in bytes. +/// Gets entry size in bytes. /// Only used for testing purposes. #[no_mangle] -pub extern "C" fn stylus_get_lru_entry_size_estimate_bytes( +pub extern "C" fn stylus_get_entry_size_estimate_bytes( module: GoSliceData, version: u16, debug: bool, ) -> u64 { match deserialize_module(module.slice(), version, debug) { Err(error) => panic!("tried to get invalid asm!: {error}"), - Ok((_, _, lru_entry_size_estimate_bytes)) => { - lru_entry_size_estimate_bytes.try_into().unwrap() + Ok((_, _, entry_size_estimate_bytes)) => { + entry_size_estimate_bytes.try_into().unwrap() } } } diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 5fbc512211..38da63013d 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -52,6 +52,11 @@ var ( stylusLRUCacheSizeHitsCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/hits", nil) stylusLRUCacheSizeMissesCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/misses", nil) stylusLRUCacheSizeDoesNotFitCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/does_not_fit", nil) + + stylusLongTermCacheSizeBytesGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/long_term/size_bytes", nil) + stylusLongTermCacheSizeCountGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/long_term/count", nil) + stylusLongTermCacheSizeHitsCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/long_term/hits", nil) + stylusLongTermCacheSizeMissesCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/long_term/misses", nil) ) func activateProgram( @@ -333,24 +338,52 @@ func SetWasmLruCacheCapacity(capacityBytes uint64) { C.stylus_set_cache_lru_capacity(u64(capacityBytes)) } -// exported for testing +func UpdateWasmCacheMetrics() { + metrics := C.stylus_get_cache_metrics() + + stylusLRUCacheSizeBytesGauge.Update(int64(metrics.lru.size_bytes)) + stylusLRUCacheSizeCountGauge.Update(int64(metrics.lru.count)) + stylusLRUCacheSizeHitsCounter.Inc(int64(metrics.lru.hits)) + stylusLRUCacheSizeMissesCounter.Inc(int64(metrics.lru.misses)) + stylusLRUCacheSizeDoesNotFitCounter.Inc(int64(metrics.lru.does_not_fit)) + + stylusLongTermCacheSizeBytesGauge.Update(int64(metrics.long_term.size_bytes)) + stylusLongTermCacheSizeCountGauge.Update(int64(metrics.long_term.count)) + stylusLongTermCacheSizeHitsCounter.Inc(int64(metrics.long_term.hits)) + stylusLongTermCacheSizeMissesCounter.Inc(int64(metrics.long_term.misses)) +} + +// Used for testing type WasmLruCacheMetrics struct { SizeBytes uint64 Count uint32 } -func GetWasmLruCacheMetrics() *WasmLruCacheMetrics { - metrics := C.stylus_get_lru_cache_metrics() +// Used for testing +type WasmLongTermCacheMetrics struct { + SizeBytes uint64 + Count uint32 +} - stylusLRUCacheSizeBytesGauge.Update(int64(metrics.size_bytes)) - stylusLRUCacheSizeCountGauge.Update(int64(metrics.count)) - stylusLRUCacheSizeHitsCounter.Inc(int64(metrics.hits)) - stylusLRUCacheSizeMissesCounter.Inc(int64(metrics.misses)) - stylusLRUCacheSizeDoesNotFitCounter.Inc(int64(metrics.does_not_fit)) +// Used for testing +type WasmCacheMetrics struct { + Lru WasmLruCacheMetrics + LongTerm WasmLongTermCacheMetrics +} - return &WasmLruCacheMetrics{ - SizeBytes: uint64(metrics.size_bytes), - Count: uint32(metrics.count), +// Used for testing +func GetWasmCacheMetrics() *WasmCacheMetrics { + metrics := C.stylus_get_cache_metrics() + + return &WasmCacheMetrics{ + Lru: WasmLruCacheMetrics{ + SizeBytes: uint64(metrics.lru.size_bytes), + Count: uint32(metrics.lru.count), + }, + LongTerm: WasmLongTermCacheMetrics{ + SizeBytes: uint64(metrics.long_term.size_bytes), + Count: uint32(metrics.long_term.count), + }, } } @@ -360,8 +393,8 @@ func ClearWasmLruCache() { } // Used for testing -func GetLruEntrySizeEstimateBytes(module []byte, version uint16, debug bool) uint64 { - return uint64(C.stylus_get_lru_entry_size_estimate_bytes(goSlice(module), u16(version), cbool(debug))) +func GetEntrySizeEstimateBytes(module []byte, version uint16, debug bool) uint64 { + return uint64(C.stylus_get_entry_size_estimate_bytes(goSlice(module), u16(version), cbool(debug))) } const DefaultTargetDescriptionArm = "arm64-linux-unknown+neon" diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index b36340757a..23573a0277 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -976,14 +976,14 @@ func (s *ExecutionEngine) Start(ctx_in context.Context) { } }) if !s.disableStylusCacheMetricsCollection { - // periodically update stylus lru cache metrics + // periodically update stylus cache metrics s.LaunchThread(func(ctx context.Context) { for { select { case <-ctx.Done(): return case <-time.After(time.Minute): - programs.GetWasmLruCacheMetrics() + programs.UpdateWasmCacheMetrics() } } }) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 1cbbf268f1..51a35b9981 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -2008,7 +2008,7 @@ func checkWasmStoreContent(t *testing.T, wasmDb ethdb.KeyValueStore, targets []s } } -func deployWasmAndGetLruEntrySizeEstimateBytes( +func deployWasmAndGetEntrySizeEstimateBytes( t *testing.T, builder *NodeBuilder, auth bind.TransactOpts, @@ -2039,12 +2039,12 @@ func deployWasmAndGetLruEntrySizeEstimateBytes( module, err := statedb.TryGetActivatedAsm(rawdb.LocalTarget(), log.ModuleHash) Require(t, err, ", wasmName:", wasmName) - lruEntrySizeEstimateBytes := programs.GetLruEntrySizeEstimateBytes(module, log.Version, true) + entrySizeEstimateBytes := programs.GetEntrySizeEstimateBytes(module, log.Version, true) // just a sanity check - if lruEntrySizeEstimateBytes == 0 { - Fatal(t, "lruEntrySizeEstimateBytes is 0, wasmName:", wasmName) + if entrySizeEstimateBytes == 0 { + Fatal(t, "entrySizeEstimateBytes is 0, wasmName:", wasmName) } - return programAddress, lruEntrySizeEstimateBytes + return programAddress, entrySizeEstimateBytes } func TestWasmLruCache(t *testing.T) { @@ -2057,9 +2057,9 @@ func TestWasmLruCache(t *testing.T) { auth.GasLimit = 32000000 auth.Value = oneEth - fallibleProgramAddress, fallibleLruEntrySizeEstimateBytes := deployWasmAndGetLruEntrySizeEstimateBytes(t, builder, auth, "fallible") - keccakProgramAddress, keccakLruEntrySizeEstimateBytes := deployWasmAndGetLruEntrySizeEstimateBytes(t, builder, auth, "keccak") - mathProgramAddress, mathLruEntrySizeEstimateBytes := deployWasmAndGetLruEntrySizeEstimateBytes(t, builder, auth, "math") + fallibleProgramAddress, fallibleLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "fallible") + keccakProgramAddress, keccakLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "keccak") + mathProgramAddress, mathLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "math") t.Log( "lruEntrySizeEstimateBytes, ", "fallible:", fallibleLruEntrySizeEstimateBytes, @@ -2068,7 +2068,7 @@ func TestWasmLruCache(t *testing.T) { ) programs.ClearWasmLruCache() - lruMetrics := programs.GetWasmLruCacheMetrics() + lruMetrics := programs.GetWasmCacheMetrics().Lru if lruMetrics.Count != 0 { t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 0, lruMetrics.Count) } @@ -2082,7 +2082,7 @@ func TestWasmLruCache(t *testing.T) { Require(t, l2client.SendTransaction(ctx, tx)) _, err := EnsureTxSucceeded(ctx, l2client, tx) Require(t, err) - lruMetrics = programs.GetWasmLruCacheMetrics() + lruMetrics = programs.GetWasmCacheMetrics().Lru if lruMetrics.Count != 0 { t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 0, lruMetrics.Count) } @@ -2098,7 +2098,7 @@ func TestWasmLruCache(t *testing.T) { Require(t, l2client.SendTransaction(ctx, tx)) _, err = EnsureTxSucceeded(ctx, l2client, tx) Require(t, err) - lruMetrics = programs.GetWasmLruCacheMetrics() + lruMetrics = programs.GetWasmCacheMetrics().Lru if lruMetrics.Count != 1 { t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 1, lruMetrics.Count) } @@ -2111,7 +2111,7 @@ func TestWasmLruCache(t *testing.T) { Require(t, l2client.SendTransaction(ctx, tx)) _, err = EnsureTxSucceeded(ctx, l2client, tx) Require(t, err) - lruMetrics = programs.GetWasmLruCacheMetrics() + lruMetrics = programs.GetWasmCacheMetrics().Lru if lruMetrics.Count != 2 { t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 2, lruMetrics.Count) } @@ -2124,7 +2124,7 @@ func TestWasmLruCache(t *testing.T) { Require(t, l2client.SendTransaction(ctx, tx)) _, err = EnsureTxSucceeded(ctx, l2client, tx) Require(t, err) - lruMetrics = programs.GetWasmLruCacheMetrics() + lruMetrics = programs.GetWasmCacheMetrics().Lru if lruMetrics.Count != 2 { t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 2, lruMetrics.Count) } From 02f1dc0508af91e8c548c82202d14a271442cd1a Mon Sep 17 00:00:00 2001 From: Diego Ximenes Date: Mon, 30 Sep 2024 17:35:04 -0300 Subject: [PATCH 09/20] Rust lint --- arbitrator/stylus/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index c16f3d7598..f5598198eb 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -11,7 +11,7 @@ use arbutil::{ format::DebugBytes, Bytes32, }; -use cache::{deserialize_module, InitCache, CacheMetrics}; +use cache::{deserialize_module, CacheMetrics, InitCache}; use evm_api::NativeRequestHandler; use eyre::ErrReport; use native::NativeInstance; @@ -387,8 +387,6 @@ pub extern "C" fn stylus_get_entry_size_estimate_bytes( ) -> u64 { match deserialize_module(module.slice(), version, debug) { Err(error) => panic!("tried to get invalid asm!: {error}"), - Ok((_, _, entry_size_estimate_bytes)) => { - entry_size_estimate_bytes.try_into().unwrap() - } + Ok((_, _, entry_size_estimate_bytes)) => entry_size_estimate_bytes.try_into().unwrap(), } } From a4784603c38e0a2b8d52c21ef4ef09e16376a83c Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 1 Oct 2024 18:50:37 +0200 Subject: [PATCH 10/20] InitCache: add items found in LRU to long term cache (if long_term_tag is 1) --- arbitrator/stylus/src/cache.rs | 17 ++++++++++++----- arbitrator/stylus/src/native.rs | 7 +++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index c1fdaaccee..21933c51cd 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -136,9 +136,10 @@ impl InitCache { } /// Retrieves a cached value, updating items as necessary. - pub fn get(module_hash: Bytes32, version: u16, debug: bool) -> Option<(Module, Store)> { - let mut cache = cache!(); + /// If long_term_tag is 1 and the item is only in LRU will insert to long term cache. + pub fn get(module_hash: Bytes32, version: u16, long_term_tag: u32, debug: bool) -> Option<(Module, Store)> { let key = CacheKey::new(module_hash, version, debug); + let mut cache = cache!(); // See if the item is in the long term cache if let Some(item) = cache.long_term.get(&key) { @@ -146,12 +147,18 @@ impl InitCache { } // See if the item is in the LRU cache, promoting if so - if let Some(item) = cache.lru.get(&key) { - let data = item.data(); + if let Some(item) = cache.lru.peek(&key).cloned() { cache.lru_counters.hits += 1; - return Some(data); + if long_term_tag == Self::ARBOS_TAG { + cache.long_term.insert(key, item.clone()); + } else { + // only calls get to move the key to the head of the LRU list + cache.lru.get(&key); + } + return Some(item.data()); } cache.lru_counters.misses += 1; + None } diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 516c6602e7..c751a670cc 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -121,13 +121,12 @@ impl> NativeInstance { let compile = CompileConfig::version(version, debug); let env = WasmEnv::new(compile, None, evm, evm_data); let module_hash = env.evm_data.module_hash; - - if let Some((module, store)) = InitCache::get(module_hash, version, debug) { - return Self::from_module(module, store, env); - } if !env.evm_data.cached { long_term_tag = 0; } + if let Some((module, store)) = InitCache::get(module_hash, version, long_term_tag, debug) { + return Self::from_module(module, store, env); + } let (module, store) = InitCache::insert(module_hash, module, version, long_term_tag, debug)?; Self::from_module(module, store, env) From d61710fa1b2ef90486d69edb0b3c9623034c6ca4 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 1 Oct 2024 22:21:21 +0200 Subject: [PATCH 11/20] rustfmt InitCache.get --- arbitrator/stylus/src/cache.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 21933c51cd..d849d39be0 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -137,7 +137,12 @@ impl InitCache { /// Retrieves a cached value, updating items as necessary. /// If long_term_tag is 1 and the item is only in LRU will insert to long term cache. - pub fn get(module_hash: Bytes32, version: u16, long_term_tag: u32, debug: bool) -> Option<(Module, Store)> { + pub fn get( + module_hash: Bytes32, + version: u16, + long_term_tag: u32, + debug: bool, + ) -> Option<(Module, Store)> { let key = CacheKey::new(module_hash, version, debug); let mut cache = cache!(); From 328a386f6eb020b2ee3a35752b8a381508729ce1 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 1 Oct 2024 22:21:43 +0200 Subject: [PATCH 12/20] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 0c3f6eba21..b1075d3786 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 0c3f6eba21cbe0196b298dfbd3fa7d51dffd627e +Subproject commit b1075d3786b28a6a3a06fe0e0ab8d1cdecc72f55 From da58307a6b051005c632e01f33be6a8599beb917 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 02:46:11 +0200 Subject: [PATCH 13/20] system_tests: fix cache tag used when wrapping wasm database in test --- system_tests/common_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 209e7c2d35..fba6aa2fc6 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -1309,7 +1309,7 @@ func createNonL1BlockChainWithStackConfig( Require(t, err) wasmData, err := stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 0, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 1, execConfig.StylusTarget.WasmTargets()) arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) @@ -1401,7 +1401,7 @@ func Create2ndNodeWithConfig( Require(t, err) wasmData, err := chainStack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 0, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 1, execConfig.StylusTarget.WasmTargets()) arbDb, err := chainStack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) From 6c69a96856c4be0118a84b554640b09b7d353ef3 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 02:52:45 +0200 Subject: [PATCH 14/20] add stylus wasm long term cache test --- arbitrator/stylus/src/lib.rs | 7 +++ arbos/programs/native.go | 5 ++ system_tests/program_test.go | 106 ++++++++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index f5598198eb..feac828989 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -377,6 +377,13 @@ pub extern "C" fn stylus_clear_lru_cache() { InitCache::clear_lru_cache() } +/// Clears long term cache. +/// Only used for testing purposes. +#[no_mangle] +pub extern "C" fn stylus_clear_long_term_cache(arbos_tag: u32) { + InitCache::clear_long_term(arbos_tag); +} + /// Gets entry size in bytes. /// Only used for testing purposes. #[no_mangle] diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 38da63013d..5baacea381 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -392,6 +392,11 @@ func ClearWasmLruCache() { C.stylus_clear_lru_cache() } +// Used for testing +func ClearWasmLongTermCache(arbos_tag uint32) { + C.stylus_clear_long_term_cache(u32(arbos_tag)) +} + // Used for testing func GetEntrySizeEstimateBytes(module []byte, version uint16, debug bool) uint64 { return uint64(C.stylus_get_entry_size_estimate_bytes(goSlice(module), u16(version), cbool(debug))) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 21081dc341..1686e212bf 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -1385,7 +1385,7 @@ func TestProgramCacheManager(t *testing.T) { isManager, err := arbWasmCache.IsCacheManager(nil, manager) assert(!isManager, err) - // athorize the manager + // authorize the manager ensure(arbOwner.AddWasmCacheManager(&ownerAuth, manager)) assert(arbWasmCache.IsCacheManager(nil, manager)) all, err := arbWasmCache.AllCacheManagers(nil) @@ -2137,3 +2137,107 @@ func TestWasmLruCache(t *testing.T) { t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", keccakLruEntrySizeEstimateBytes+mathLruEntrySizeEstimateBytes, lruMetrics.SizeBytes) } } + +func TestWasmLongTermCache(t *testing.T) { + builder, ownerAuth, cleanup := setupProgramTest(t, true) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + manager, tx, _, err := mocksgen.DeploySimpleCacheManager(&ownerAuth, l2client) + ensure(tx, err) + + arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) + Require(t, err) + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&ownerAuth, 10_000)) + + ownerAuth.GasLimit = 32000000 + ownerAuth.Value = oneEth + + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "math") + t.Log( + "lruEntrySizeEstimateBytes, ", + "fallible:", fallibleEntrySize, + "keccak:", keccakEntrySize, + "math:", mathEntrySize, + ) + + isManager, err := arbWasmCache.IsCacheManager(nil, manager) + Require(t, err) + t.Log("isManager", isManager) + ownerAuth.Value = common.Big0 + ensure(arbOwner.AddWasmCacheManager(&ownerAuth, manager)) + + checkLongTermMetrics := func(expected programs.WasmLongTermCacheMetrics) { + t.Helper() + longTermMetrics := programs.GetWasmCacheMetrics().LongTerm + if longTermMetrics.Count != expected.Count { + t.Fatalf("longTermMetrics.Count, expected: %v, actual: %v", expected.Count, longTermMetrics.Count) + } + if longTermMetrics.SizeBytes != expected.SizeBytes { + t.Fatalf("longTermMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, longTermMetrics.SizeBytes) + } + } + + programs.ClearWasmLongTermCache(1) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + // fallible wasm program will not be cached since caching is not set for this program + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + ensure(arbWasmCache.CacheProgram(&ownerAuth, fallibleProgramAddress)) + // fallible wasm program will be cached + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // keccak wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, keccakProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + + // math wasm program will not be cached + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + + // math wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, mathProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + Count: 3, + SizeBytes: fallibleEntrySize + keccakEntrySize + mathEntrySize, + }) +} From 80c931869df8820a5f9ae3fe3242936f52b440db Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 17:05:05 +0200 Subject: [PATCH 15/20] system_test: use stylus long term cache only in specific tests --- system_tests/common_test.go | 30 +++++++++++++++++++++++------- system_tests/program_test.go | 4 +++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index fba6aa2fc6..d2fda0e135 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -249,6 +249,7 @@ type NodeBuilder struct { initMessage *arbostypes.ParsedInitMessage l3InitMessage *arbostypes.ParsedInitMessage withProdConfirmPeriodBlocks bool + wasmCacheTag uint32 // Created nodes L1 *TestClient @@ -352,6 +353,15 @@ func (b *NodeBuilder) WithExtraArchs(targets []string) *NodeBuilder { return b } +func (b *NodeBuilder) WithStylusLongTermCache(enabled bool) *NodeBuilder { + if enabled { + b.wasmCacheTag = 1 + } else { + b.wasmCacheTag = 0 + } + return b +} + func (b *NodeBuilder) Build(t *testing.T) func() { b.CheckConfig(t) if b.withL1 { @@ -425,6 +435,8 @@ func buildOnParentChain( initMessage *arbostypes.ParsedInitMessage, addresses *chaininfo.RollupAddresses, + + wasmCacheTag uint32, ) *TestClient { if parentChainTestClient == nil { t.Fatal("must build parent chain before building chain") @@ -436,7 +448,7 @@ func buildOnParentChain( var arbDb ethdb.Database var blockchain *core.BlockChain _, chainTestClient.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( - t, chainInfo, dataDir, chainConfig, initMessage, stackConfig, execConfig) + t, chainInfo, dataDir, chainConfig, initMessage, stackConfig, execConfig, wasmCacheTag) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc @@ -524,6 +536,8 @@ func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { b.l3InitMessage, b.l3Addresses, + + b.wasmCacheTag, ) return func() { @@ -552,6 +566,8 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { b.initMessage, b.addresses, + + b.wasmCacheTag, ) return func() { @@ -573,7 +589,7 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { var arbDb ethdb.Database var blockchain *core.BlockChain b.L2Info, b.L2.Stack, chainDb, arbDb, blockchain = createL2BlockChain( - t, b.L2Info, b.dataDir, b.chainConfig, b.execConfig) + t, b.L2Info, b.dataDir, b.chainConfig, b.execConfig, b.wasmCacheTag) Require(t, b.execConfig.Validate()) execConfig := b.execConfig @@ -624,7 +640,7 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { } b.L2.cleanup() - l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.initMessage, b.l2StackConfig, b.execConfig) + l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.initMessage, b.l2StackConfig, b.execConfig, b.wasmCacheTag) execConfigFetcher := func() *gethexec.Config { return b.execConfig } execNode, err := gethexec.CreateExecutionNode(b.ctx, stack, chainDb, blockchain, nil, execConfigFetcher) @@ -1284,13 +1300,13 @@ func deployOnParentChain( } func createL2BlockChain( - t *testing.T, l2info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, execConfig *gethexec.Config, + t *testing.T, l2info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, execConfig *gethexec.Config, wasmCacheTag uint32, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { - return createNonL1BlockChainWithStackConfig(t, l2info, dataDir, chainConfig, nil, nil, execConfig) + return createNonL1BlockChainWithStackConfig(t, l2info, dataDir, chainConfig, nil, nil, execConfig, wasmCacheTag) } func createNonL1BlockChainWithStackConfig( - t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, + t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, wasmCacheTag uint32, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { if info == nil { info = NewArbTestInfo(t, chainConfig.ChainID) @@ -1309,7 +1325,7 @@ func createNonL1BlockChainWithStackConfig( Require(t, err) wasmData, err := stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 1, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, wasmCacheTag, execConfig.StylusTarget.WasmTargets()) arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 1686e212bf..a8c325daf3 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -2139,7 +2139,9 @@ func TestWasmLruCache(t *testing.T) { } func TestWasmLongTermCache(t *testing.T) { - builder, ownerAuth, cleanup := setupProgramTest(t, true) + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { + builder.WithStylusLongTermCache(true) + }) ctx := builder.ctx l2info := builder.L2Info l2client := builder.L2.Client From 4676459bf41d35cdc59a3dac3dfec6026a2e9923 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 23:04:42 +0200 Subject: [PATCH 16/20] fix tracing long term cache size when adding item from lru --- arbitrator/stylus/src/cache.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 1502fa82e6..0a4b73c2a6 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -184,6 +184,7 @@ impl InitCache { cache.lru_counters.hits += 1; if long_term_tag == Self::ARBOS_TAG { cache.long_term.insert(key, item.clone()); + cache.long_term_size_bytes += item.entry_size_estimate_bytes; } else { // only calls get to move the key to the head of the LRU list cache.lru.get(&key); From 0ab06c87cce2e8199580b8b69349711589ec2f37 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 23:10:36 +0200 Subject: [PATCH 17/20] add test for adding wasm from lru cache to long term cache --- system_tests/program_test.go | 222 +++++++++++++++++++++++++++++++---- 1 file changed, 197 insertions(+), 25 deletions(-) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index a8c325daf3..6ca04976e5 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -2138,6 +2138,28 @@ func TestWasmLruCache(t *testing.T) { } } +func checkLongTermCacheMetrics(t *testing.T, expected programs.WasmLongTermCacheMetrics) { + t.Helper() + longTermMetrics := programs.GetWasmCacheMetrics().LongTerm + if longTermMetrics.Count != expected.Count { + t.Fatalf("longTermMetrics.Count, expected: %v, actual: %v", expected.Count, longTermMetrics.Count) + } + if longTermMetrics.SizeBytes != expected.SizeBytes { + t.Fatalf("longTermMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, longTermMetrics.SizeBytes) + } +} + +func checkLruCacheMetrics(t *testing.T, expected programs.WasmLruCacheMetrics) { + t.Helper() + lruMetrics := programs.GetWasmCacheMetrics().Lru + if lruMetrics.Count != expected.Count { + t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", expected.Count, lruMetrics.Count) + } + if lruMetrics.SizeBytes != expected.SizeBytes { + t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, lruMetrics.SizeBytes) + } +} + func TestWasmLongTermCache(t *testing.T) { builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { builder.WithStylusLongTermCache(true) @@ -2155,9 +2177,6 @@ func TestWasmLongTermCache(t *testing.T) { return receipt } - manager, tx, _, err := mocksgen.DeploySimpleCacheManager(&ownerAuth, l2client) - ensure(tx, err) - arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) Require(t, err) arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) @@ -2177,33 +2196,18 @@ func TestWasmLongTermCache(t *testing.T) { "math:", mathEntrySize, ) - isManager, err := arbWasmCache.IsCacheManager(nil, manager) - Require(t, err) - t.Log("isManager", isManager) ownerAuth.Value = common.Big0 - ensure(arbOwner.AddWasmCacheManager(&ownerAuth, manager)) - - checkLongTermMetrics := func(expected programs.WasmLongTermCacheMetrics) { - t.Helper() - longTermMetrics := programs.GetWasmCacheMetrics().LongTerm - if longTermMetrics.Count != expected.Count { - t.Fatalf("longTermMetrics.Count, expected: %v, actual: %v", expected.Count, longTermMetrics.Count) - } - if longTermMetrics.SizeBytes != expected.SizeBytes { - t.Fatalf("longTermMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, longTermMetrics.SizeBytes) - } - } programs.ClearWasmLongTermCache(1) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 0, SizeBytes: 0, }) // fallible wasm program will not be cached since caching is not set for this program - tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 0, SizeBytes: 0, }) @@ -2212,7 +2216,7 @@ func TestWasmLongTermCache(t *testing.T) { // fallible wasm program will be cached tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 1, SizeBytes: fallibleEntrySize, }) @@ -2221,7 +2225,7 @@ func TestWasmLongTermCache(t *testing.T) { ensure(arbWasmCache.CacheProgram(&ownerAuth, keccakProgramAddress)) tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 2, SizeBytes: fallibleEntrySize + keccakEntrySize, }) @@ -2229,7 +2233,7 @@ func TestWasmLongTermCache(t *testing.T) { // math wasm program will not be cached tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 2, SizeBytes: fallibleEntrySize + keccakEntrySize, }) @@ -2238,8 +2242,176 @@ func TestWasmLongTermCache(t *testing.T) { ensure(arbWasmCache.CacheProgram(&ownerAuth, mathProgramAddress)) tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) - checkLongTermMetrics(programs.WasmLongTermCacheMetrics{ + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ Count: 3, SizeBytes: fallibleEntrySize + keccakEntrySize + mathEntrySize, }) + + statedb, err := builder.L2.ExecNode.Backend.ArbInterface().BlockChain().State() + Require(t, err) + fallibleProgramHash := statedb.GetCodeHash(fallibleProgramAddress) + keccakProgramHash := statedb.GetCodeHash(keccakProgramAddress) + mathProgramHash := statedb.GetCodeHash(mathProgramAddress) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, keccakProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + mathEntrySize, + }) + + // keccak wasm program will not be cached + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + mathEntrySize, + }) + + // keccak wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, keccakProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 3, + SizeBytes: fallibleEntrySize + keccakEntrySize + mathEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, fallibleProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, mathProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: keccakEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, keccakProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) +} + +func TestRepopulateWasmLongTermCacheFromLru(t *testing.T) { + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { + builder.WithStylusLongTermCache(true) + }) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) + Require(t, err) + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&ownerAuth, 10_000)) + + ownerAuth.GasLimit = 32000000 + ownerAuth.Value = oneEth + + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "math") + + ownerAuth.Value = common.Big0 + + programs.ClearWasmLongTermCache(1) + programs.ClearWasmLruCache() + // only 2 out of 3 programs should fit lru + programs.SetWasmLruCacheCapacity( + fallibleEntrySize + keccakEntrySize + mathEntrySize - 1, + ) + + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + ensure(arbWasmCache.CacheProgram(&ownerAuth, fallibleProgramAddress)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // clear long term cache to emulate restart + programs.ClearWasmLongTermCache(1) + programs.ClearWasmLruCache() + + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + nonce := builder.L2Info.GetInfoWithPrivKey("Owner").Nonce.Load() + tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + _, err = arbutil.SendTxAsCall(ctx, l2client, tx, l2info.GetAddress("Owner"), nil, true) + Require(t, err) + // restore nonce in L2Info + builder.L2Info.GetInfoWithPrivKey("Owner").Nonce.Store(nonce) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // mathProgram should end up in lru cache and as result fallibleProgram should be evicted as least recently used item (tx that restores the program back to long term cache shouldn't promote the lru item); fallibleProgram should remain in long term cache + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) } From f65156d6a6f3d61110503b5ba97fefa50c02405e Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 3 Oct 2024 23:38:16 +0200 Subject: [PATCH 18/20] don't clone cache item twice --- arbitrator/stylus/src/cache.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 0a4b73c2a6..208f45e26c 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -182,14 +182,15 @@ impl InitCache { // See if the item is in the LRU cache, promoting if so if let Some(item) = cache.lru.peek(&key).cloned() { cache.lru_counters.hits += 1; + let data = item.data(); if long_term_tag == Self::ARBOS_TAG { - cache.long_term.insert(key, item.clone()); cache.long_term_size_bytes += item.entry_size_estimate_bytes; + cache.long_term.insert(key, item); } else { // only calls get to move the key to the head of the LRU list cache.lru.get(&key); } - return Some(item.data()); + return Some(data); } cache.lru_counters.misses += 1; From d5f5f11c6e880dca803fb9ef141f0971b41ef743 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 4 Oct 2024 13:14:28 +0200 Subject: [PATCH 19/20] refactor wasm lru cache test --- system_tests/program_test.go | 99 ++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 6ca04976e5..aab207e0f6 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -2059,83 +2059,68 @@ func TestWasmLruCache(t *testing.T) { l2client := builder.L2.Client defer cleanup() + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + auth.GasLimit = 32000000 auth.Value = oneEth - fallibleProgramAddress, fallibleLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "fallible") - keccakProgramAddress, keccakLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "keccak") - mathProgramAddress, mathLruEntrySizeEstimateBytes := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "math") + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "math") t.Log( "lruEntrySizeEstimateBytes, ", - "fallible:", fallibleLruEntrySizeEstimateBytes, - "keccak:", keccakLruEntrySizeEstimateBytes, - "math:", mathLruEntrySizeEstimateBytes, + "fallible:", fallibleEntrySize, + "keccak:", keccakEntrySize, + "math:", mathEntrySize, ) programs.ClearWasmLruCache() - lruMetrics := programs.GetWasmCacheMetrics().Lru - if lruMetrics.Count != 0 { - t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 0, lruMetrics.Count) - } - if lruMetrics.SizeBytes != 0 { - t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", 0, lruMetrics.SizeBytes) - } + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) - programs.SetWasmLruCacheCapacity(fallibleLruEntrySizeEstimateBytes - 1) + programs.SetWasmLruCacheCapacity(fallibleEntrySize - 1) // fallible wasm program will not be cached since its size is greater than lru cache capacity tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) - Require(t, l2client.SendTransaction(ctx, tx)) - _, err := EnsureTxSucceeded(ctx, l2client, tx) - Require(t, err) - lruMetrics = programs.GetWasmCacheMetrics().Lru - if lruMetrics.Count != 0 { - t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 0, lruMetrics.Count) - } - if lruMetrics.SizeBytes != 0 { - t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", 0, lruMetrics.SizeBytes) - } + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) programs.SetWasmLruCacheCapacity( - fallibleLruEntrySizeEstimateBytes + keccakLruEntrySizeEstimateBytes + mathLruEntrySizeEstimateBytes - 1, + fallibleEntrySize + keccakEntrySize + mathEntrySize - 1, ) // fallible wasm program will be cached tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) - Require(t, l2client.SendTransaction(ctx, tx)) - _, err = EnsureTxSucceeded(ctx, l2client, tx) - Require(t, err) - lruMetrics = programs.GetWasmCacheMetrics().Lru - if lruMetrics.Count != 1 { - t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 1, lruMetrics.Count) - } - if lruMetrics.SizeBytes != fallibleLruEntrySizeEstimateBytes { - t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", fallibleLruEntrySizeEstimateBytes, lruMetrics.SizeBytes) - } + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) // keccak wasm program will be cached tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) - Require(t, l2client.SendTransaction(ctx, tx)) - _, err = EnsureTxSucceeded(ctx, l2client, tx) - Require(t, err) - lruMetrics = programs.GetWasmCacheMetrics().Lru - if lruMetrics.Count != 2 { - t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 2, lruMetrics.Count) - } - if lruMetrics.SizeBytes != fallibleLruEntrySizeEstimateBytes+keccakLruEntrySizeEstimateBytes { - t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", fallibleLruEntrySizeEstimateBytes+keccakLruEntrySizeEstimateBytes, lruMetrics.SizeBytes) - } + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) // math wasm program will be cached, but fallible will be evicted since (fallible + keccak + math) > lruCacheCapacity tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) - Require(t, l2client.SendTransaction(ctx, tx)) - _, err = EnsureTxSucceeded(ctx, l2client, tx) - Require(t, err) - lruMetrics = programs.GetWasmCacheMetrics().Lru - if lruMetrics.Count != 2 { - t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", 2, lruMetrics.Count) - } - if lruMetrics.SizeBytes != keccakLruEntrySizeEstimateBytes+mathLruEntrySizeEstimateBytes { - t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", keccakLruEntrySizeEstimateBytes+mathLruEntrySizeEstimateBytes, lruMetrics.SizeBytes) - } + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) } func checkLongTermCacheMetrics(t *testing.T, expected programs.WasmLongTermCacheMetrics) { @@ -2403,7 +2388,9 @@ func TestRepopulateWasmLongTermCacheFromLru(t *testing.T) { SizeBytes: fallibleEntrySize, }) - // mathProgram should end up in lru cache and as result fallibleProgram should be evicted as least recently used item (tx that restores the program back to long term cache shouldn't promote the lru item); fallibleProgram should remain in long term cache + // mathProgram should end up in lru cache and + // as result fallibleProgram should be evicted as least recently used item (tx that restores the program back to long term cache shouldn't promote the lru item); + // fallibleProgram should remain in long term cache tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) ensure(tx, l2client.SendTransaction(ctx, tx)) checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ From fe2233ffb46f98a95398335c793c2725490da595 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 4 Oct 2024 18:55:49 +0200 Subject: [PATCH 20/20] avoid unncessary cloning of cache item data --- arbitrator/stylus/src/cache.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index 208f45e26c..6192a30eff 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -182,15 +182,14 @@ impl InitCache { // See if the item is in the LRU cache, promoting if so if let Some(item) = cache.lru.peek(&key).cloned() { cache.lru_counters.hits += 1; - let data = item.data(); if long_term_tag == Self::ARBOS_TAG { cache.long_term_size_bytes += item.entry_size_estimate_bytes; - cache.long_term.insert(key, item); + cache.long_term.insert(key, item.clone()); } else { // only calls get to move the key to the head of the LRU list cache.lru.get(&key); } - return Some(data); + return Some((item.module, Store::new(item.engine))); } cache.lru_counters.misses += 1;