From 3abb04ce2f62b0539bde5eb88e0b55b1f5e7e745 Mon Sep 17 00:00:00 2001
From: Darioush Jalali <darioush.jalali@avalabs.org>
Date: Wed, 13 Nov 2024 14:42:19 -0800
Subject: [PATCH] sync subnet-evm to 981830ed + avalanchego
 v1.12.0-initial-poc.9 (#686)

---
 .github/pull_request_template.md            |   4 +
 README.md                                   |   2 +-
 core/blockchain.go                          |  18 +-
 core/chain_makers.go                        |   2 +-
 core/genesis.go                             |   2 +-
 core/state/state_test.go                    |   8 +-
 core/state/statedb.go                       |  20 +-
 core/state/statedb_fuzz_test.go             |   2 +-
 core/state/statedb_test.go                  |  36 +--
 core/state/sync_test.go                     |   2 +-
 core/test_blockchain.go                     |   2 +-
 core/txpool/blobpool/blobpool_test.go       |  12 +-
 eth/api_debug_test.go                       |   6 +-
 eth/state_accessor.go                       |   7 +-
 go.mod                                      |  28 +--
 go.sum                                      |  61 +++--
 plugin/evm/block.go                         |  29 +--
 plugin/evm/gossip_test.go                   |   2 +-
 plugin/evm/import_tx.go                     |   2 +-
 plugin/evm/shared_memory_writer.go          |  37 ---
 plugin/evm/syncervm_test.go                 |   2 +-
 plugin/evm/tx.go                            |  11 +
 plugin/evm/tx_gossip_test.go                |  38 +--
 plugin/evm/vm.go                            | 176 ++++++++------
 plugin/evm/vm_warp_test.go                  | 115 +++++++--
 precompile/precompileconfig/config.go       |  13 +-
 scripts/versions.sh                         |   2 +-
 tests/state_test_util.go                    |   2 +-
 triedb/database.go                          |  11 -
 triedb/hashdb/database.go                   |  39 ++-
 utils/snow.go                               |  55 ++++-
 warp/backend.go                             | 114 ++++-----
 warp/backend_test.go                        |  77 ++----
 warp/handlers/signature_request.go          |  22 +-
 warp/handlers/signature_request_p2p.go      | 151 ------------
 warp/handlers/signature_request_p2p_test.go | 232 ------------------
 warp/handlers/signature_request_test.go     |  15 +-
 warp/handlers/stats.go                      |  26 +-
 warp/service.go                             |   8 +-
 warp/verifier_backend.go                    |  64 +++++
 warp/verifier_backend_test.go               | 255 ++++++++++++++++++++
 warp/verifier_stats.go                      |  29 +++
 42 files changed, 854 insertions(+), 885 deletions(-)
 delete mode 100644 plugin/evm/shared_memory_writer.go
 delete mode 100644 warp/handlers/signature_request_p2p.go
 delete mode 100644 warp/handlers/signature_request_p2p_test.go
 create mode 100644 warp/verifier_backend.go
 create mode 100644 warp/verifier_backend_test.go
 create mode 100644 warp/verifier_stats.go

diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 8200c0597f..7751a8146f 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -3,3 +3,7 @@
 ## How this works
 
 ## How this was tested
+
+## Need to be documented?
+
+## Need to update RELEASES.md?
diff --git a/README.md b/README.md
index 69a11a6c43..40cd3a8407 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [Avalanche](https://docs.avax.network/intro) is a network composed of multiple blockchains.
 Each blockchain is an instance of a Virtual Machine (VM), much like an object in an object-oriented language is an instance of a class.
 That is, the VM defines the behavior of the blockchain.
-Coreth (from core Ethereum) is the [Virtual Machine (VM)](https://docs.avax.network/learn/avalanche/virtual-machines) that defines the Contract Chain (C-Chain).
+Coreth (from core Ethereum) is the [Virtual Machine (VM)](https://docs.avax.network/learn/virtual-machines) that defines the Contract Chain (C-Chain).
 This chain implements the Ethereum Virtual Machine and supports Solidity smart contracts as well as most other Ethereum client functionality.
 
 ## Building
diff --git a/core/blockchain.go b/core/blockchain.go
index 2dcd2738a6..db3a49bdb0 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -177,10 +177,11 @@ type CacheConfig struct {
 // triedbConfig derives the configures for trie database.
 func (c *CacheConfig) triedbConfig() *triedb.Config {
 	config := &triedb.Config{Preimages: c.Preimages}
-	if c.StateScheme == rawdb.HashScheme {
+	if c.StateScheme == rawdb.HashScheme || c.StateScheme == "" {
 		config.HashDB = &hashdb.Config{
-			CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
-			StatsPrefix:    trieCleanCacheStatsNamespace,
+			CleanCacheSize:                  c.TrieCleanLimit * 1024 * 1024,
+			StatsPrefix:                     trieCleanCacheStatsNamespace,
+			ReferenceRootAtomicallyOnUpdate: true,
 		}
 	}
 	if c.StateScheme == rawdb.PathScheme {
@@ -1161,9 +1162,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
 	// diff layer for the block.
 	var err error
 	if bc.snaps == nil {
-		_, err = state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), true)
+		_, err = state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
 	} else {
-		_, err = state.CommitWithSnap(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash(), true)
+		_, err = state.CommitWithSnap(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash())
 	}
 	if err != nil {
 		return err
@@ -1695,9 +1696,9 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block)
 	// If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot
 	// diff layer for the block.
 	if bc.snaps == nil {
-		return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), false)
+		return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()))
 	}
-	return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash(), false)
+	return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash())
 }
 
 // initSnapshot instantiates a Snapshot instance and adds it to [bc]
@@ -1838,8 +1839,7 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error
 		// Flatten snapshot if initialized, holding a reference to the state root until the next block
 		// is processed.
 		if err := bc.flattenSnapshot(func() error {
-			triedb.Reference(root, common.Hash{})
-			if previousRoot != (common.Hash{}) {
+			if previousRoot != (common.Hash{}) && previousRoot != root {
 				triedb.Dereference(previousRoot)
 			}
 			previousRoot = root
diff --git a/core/chain_makers.go b/core/chain_makers.go
index f8831ade81..d5ffd53074 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -298,7 +298,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
 		}
 
 		// Write state changes to db
-		root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), false)
+		root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
 		if err != nil {
 			panic(fmt.Sprintf("state write error: %v", err))
 		}
diff --git a/core/genesis.go b/core/genesis.go
index 494a16f81c..07d3ba072d 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -299,7 +299,7 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo
 		}
 	}
 
-	statedb.Commit(0, false, false)
+	statedb.Commit(0, false)
 	// Commit newly generated states into disk if it's not empty.
 	if root != types.EmptyRootHash {
 		if err := triedb.Commit(root, true); err != nil {
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 0d6f7d6445..3d6dadf209 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -68,7 +68,7 @@ func TestDump(t *testing.T) {
 	// write some of them to the trie
 	s.state.updateStateObject(obj1)
 	s.state.updateStateObject(obj2)
-	root, _ := s.state.Commit(0, false, false)
+	root, _ := s.state.Commit(0, false)
 
 	// check that DumpToCollector contains the state objects that are in trie
 	s.state, _ = New(root, tdb, nil)
@@ -130,7 +130,7 @@ func TestIterativeDump(t *testing.T) {
 	// write some of them to the trie
 	s.state.updateStateObject(obj1)
 	s.state.updateStateObject(obj2)
-	root, _ := s.state.Commit(0, false, false)
+	root, _ := s.state.Commit(0, false)
 	s.state, _ = New(root, tdb, nil)
 
 	b := &bytes.Buffer{}
@@ -156,7 +156,7 @@ func TestNull(t *testing.T) {
 	var value common.Hash
 
 	s.state.SetState(address, common.Hash{}, value)
-	s.state.Commit(0, false, false)
+	s.state.Commit(0, false)
 
 	if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) {
 		t.Errorf("expected empty current value, got %x", value)
@@ -228,7 +228,7 @@ func TestSnapshot2(t *testing.T) {
 	so0.deleted = false
 	state.setStateObject(so0)
 
-	root, _ := state.Commit(0, false, false)
+	root, _ := state.Commit(0, false)
 	state, _ = New(root, state.db, nil)
 
 	// and one with deleted == true
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 9eda070321..3096589b6e 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -1263,14 +1263,14 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
 }
 
 // Commit writes the state to the underlying in-memory trie database.
-func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, referenceRoot bool) (common.Hash, error) {
-	return s.commit(block, deleteEmptyObjects, nil, common.Hash{}, common.Hash{}, referenceRoot)
+func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
+	return s.commit(block, deleteEmptyObjects, nil, common.Hash{}, common.Hash{})
 }
 
 // CommitWithSnap writes the state to the underlying in-memory trie database and
 // generates a snapshot layer for the newly committed state.
-func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
-	return s.commit(block, deleteEmptyObjects, snaps, blockHash, parentHash, referenceRoot)
+func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) {
+	return s.commit(block, deleteEmptyObjects, snaps, blockHash, parentHash)
 }
 
 // Once the state is committed, tries cached in stateDB (including account
@@ -1280,7 +1280,7 @@ func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *s
 //
 // The associated block number of the state transition is also provided
 // for more chain context.
-func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) {
+func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) {
 	// Short circuit in case any database failure occurred earlier.
 	if s.dbErr != nil {
 		return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@@ -1389,14 +1389,8 @@ func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.
 	if root != origin {
 		start := time.Now()
 		set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)
-		if referenceRoot {
-			if err := s.db.TrieDB().UpdateAndReferenceRoot(root, origin, block, nodes, set); err != nil {
-				return common.Hash{}, err
-			}
-		} else {
-			if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
-				return common.Hash{}, err
-			}
+		if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
+			return common.Hash{}, err
 		}
 		s.originalRoot = root
 		if metrics.EnabledExpensive {
diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go
index f694cb2a0f..9cbbffa870 100644
--- a/core/state/statedb_fuzz_test.go
+++ b/core/state/statedb_fuzz_test.go
@@ -233,7 +233,7 @@ func (test *stateTest) run() bool {
 		} else {
 			state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
 		}
-		nroot, err := state.Commit(0, true, false) // call commit at the block boundary
+		nroot, err := state.Commit(0, true) // call commit at the block boundary
 		if err != nil {
 			panic(err)
 		}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 7ae230fbed..88799e3c3e 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -127,7 +127,7 @@ func TestIntermediateLeaks(t *testing.T) {
 	}
 
 	// Commit and cross check the databases.
-	transRoot, err := transState.Commit(0, false, false)
+	transRoot, err := transState.Commit(0, false)
 	if err != nil {
 		t.Fatalf("failed to commit transition state: %v", err)
 	}
@@ -135,7 +135,7 @@ func TestIntermediateLeaks(t *testing.T) {
 		t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
 	}
 
-	finalRoot, err := finalState.Commit(0, false, false)
+	finalRoot, err := finalState.Commit(0, false)
 	if err != nil {
 		t.Fatalf("failed to commit final state: %v", err)
 	}
@@ -543,7 +543,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
 func TestTouchDelete(t *testing.T) {
 	s := newStateEnv()
 	s.state.getOrNewStateObject(common.Address{})
-	root, _ := s.state.Commit(0, false, false)
+	root, _ := s.state.Commit(0, false)
 	s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap)
 
 	snapshot := s.state.Snapshot()
@@ -631,7 +631,7 @@ func TestCopyCommitCopy(t *testing.T) {
 		t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval)
 	}
 	// Commit state, ensure states can be loaded from disk
-	root, _ := state.Commit(0, false, false)
+	root, _ := state.Commit(0, false)
 	state, _ = New(root, tdb, nil)
 	if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
 		t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
@@ -745,7 +745,7 @@ func TestCommitCopy(t *testing.T) {
 		t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
 	}
 	// Copy the committed state database, the copied one is not functional.
-	state.Commit(0, true, false)
+	state.Commit(0, true)
 	copied := state.Copy()
 	if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 {
 		t.Fatalf("unexpected balance: have %v", balance)
@@ -779,7 +779,7 @@ func TestDeleteCreateRevert(t *testing.T) {
 	addr := common.BytesToAddress([]byte("so"))
 	state.SetBalance(addr, uint256.NewInt(1))
 
-	root, _ := state.Commit(0, false, false)
+	root, _ := state.Commit(0, false)
 	state, _ = NewWithSnapshot(root, state.db, state.snap)
 
 	// Simulate self-destructing in one transaction, then create-reverting in another
@@ -791,7 +791,7 @@ func TestDeleteCreateRevert(t *testing.T) {
 	state.RevertToSnapshot(id)
 
 	// Commit the entire state and make sure we don't crash and have the correct state
-	root, _ = state.Commit(0, true, false)
+	root, _ = state.Commit(0, true)
 	state, _ = NewWithSnapshot(root, state.db, state.snap)
 
 	if state.getStateObject(addr) != nil {
@@ -834,7 +834,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
 		a2 := common.BytesToAddress([]byte("another"))
 		state.SetBalance(a2, uint256.NewInt(100))
 		state.SetCode(a2, []byte{1, 2, 4})
-		root, _ = state.Commit(0, false, false)
+		root, _ = state.Commit(0, false)
 		t.Logf("root: %x", root)
 		// force-flush
 		tdb.Commit(root, false)
@@ -858,7 +858,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
 	}
 	// Modify the state
 	state.SetBalance(addr, uint256.NewInt(2))
-	root, err := state.Commit(0, false, false)
+	root, err := state.Commit(0, false)
 	if err == nil {
 		t.Fatalf("expected error, got root :%x", root)
 	}
@@ -1044,7 +1044,7 @@ func TestMultiCoinOperations(t *testing.T) {
 	assetID := common.Hash{2}
 
 	s.state.getOrNewStateObject(addr)
-	root, _ := s.state.Commit(0, false, false)
+	root, _ := s.state.Commit(0, false)
 	s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap)
 
 	s.state.AddBalance(addr, new(uint256.Int))
@@ -1101,14 +1101,14 @@ func TestMultiCoinSnapshot(t *testing.T) {
 	assertBalances(10, 0, 0)
 
 	// Commit and get the new root
-	root, _ = stateDB.Commit(0, false, false)
+	root, _ = stateDB.Commit(0, false)
 	assertBalances(10, 0, 0)
 
 	// Create a new state from the latest root, add a multicoin balance, and
 	// commit it to the tree.
 	stateDB, _ = New(root, sdb, snapTree)
 	stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(10))
-	root, _ = stateDB.Commit(0, false, false)
+	root, _ = stateDB.Commit(0, false)
 	assertBalances(10, 10, 0)
 
 	// Add more layers than the cap and ensure the balances and layers are correct
@@ -1116,7 +1116,7 @@ func TestMultiCoinSnapshot(t *testing.T) {
 		stateDB, _ = New(root, sdb, snapTree)
 		stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1))
 		stateDB.AddBalanceMultiCoin(addr, assetID2, big.NewInt(2))
-		root, _ = stateDB.Commit(0, false, false)
+		root, _ = stateDB.Commit(0, false)
 	}
 	assertBalances(10, 266, 512)
 
@@ -1125,7 +1125,7 @@ func TestMultiCoinSnapshot(t *testing.T) {
 	stateDB, _ = New(root, sdb, snapTree)
 	stateDB.AddBalance(addr, uint256.NewInt(1))
 	stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1))
-	root, _ = stateDB.Commit(0, false, false)
+	root, _ = stateDB.Commit(0, false)
 	stateDB, _ = New(root, sdb, snapTree)
 	assertBalances(11, 267, 512)
 }
@@ -1147,7 +1147,7 @@ func TestGenerateMultiCoinAccounts(t *testing.T) {
 		t.Fatal(err)
 	}
 	stateDB.SetBalanceMultiCoin(addr, assetID, assetBalance)
-	root, err := stateDB.Commit(0, false, false)
+	root, err := stateDB.Commit(0, false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1207,7 +1207,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
 			state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
 		}
 	}
-	root, err := state.Commit(0, false, false)
+	root, err := state.Commit(0, false)
 	if err != nil {
 		t.Fatalf("failed to commit state trie: %v", err)
 	}
@@ -1286,7 +1286,7 @@ func TestResetObject(t *testing.T) {
 	state.CreateAccount(addr)
 	state.SetBalance(addr, uint256.NewInt(2))
 	state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
-	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false)
+	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{})
 
 	// Ensure the original account is wiped properly
 	snap := snaps.Snapshot(root)
@@ -1317,7 +1317,7 @@ func TestDeleteStorage(t *testing.T) {
 		value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32())
 		state.SetState(addr, slot, value)
 	}
-	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false)
+	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{})
 	// Init phase done, create two states, one with snap and one without
 	fastState, _ := New(root, db, snaps)
 	slowState, _ := New(root, db, nil)
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index 5b1c9e3866..17444dfe89 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -76,7 +76,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
 		}
 		accounts = append(accounts, acc)
 	}
-	root, _ := state.Commit(0, false, false)
+	root, _ := state.Commit(0, false)
 
 	// Return the generated state
 	return db, sdb, nodeDb, root, accounts
diff --git a/core/test_blockchain.go b/core/test_blockchain.go
index 41d5657b92..9c22ff0338 100644
--- a/core/test_blockchain.go
+++ b/core/test_blockchain.go
@@ -1502,7 +1502,7 @@ func checkTxIndicesHelper(t *testing.T, expectedTail *uint64, indexedFrom uint64
 		require.EventuallyWithTf(t,
 			func(c *assert.CollectT) {
 				stored = *rawdb.ReadTxIndexTail(db)
-				require.Equalf(t, tailValue, stored, "expected tail to be %d, found %d", tailValue, stored)
+				assert.Equalf(c, tailValue, stored, "expected tail to be %d, found %d", tailValue, stored)
 			},
 			30*time.Second, 500*time.Millisecond, "expected tail to be %d eventually", tailValue)
 	}
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
index 5290153bba..0e993ca9dc 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -584,7 +584,7 @@ func TestOpenDrops(t *testing.T) {
 	statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000))
 	statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000))
 	statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000))
-	statedb.Commit(0, true, false)
+	statedb.Commit(0, true)
 
 	chain := &testBlockChain{
 		config:  testChainConfig,
@@ -703,7 +703,7 @@ func TestOpenIndex(t *testing.T) {
 	// Create a blob pool out of the pre-seeded data
 	statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
 	statedb.AddBalance(addr, uint256.NewInt(1_000_000_000))
-	statedb.Commit(0, true, false)
+	statedb.Commit(0, true)
 
 	chain := &testBlockChain{
 		config:  testChainConfig,
@@ -806,7 +806,7 @@ func TestOpenHeap(t *testing.T) {
 	statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000))
 	statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000))
 	statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000))
-	statedb.Commit(0, true, false)
+	statedb.Commit(0, true)
 
 	chain := &testBlockChain{
 		config:  testChainConfig,
@@ -887,7 +887,7 @@ func TestOpenCap(t *testing.T) {
 		statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000))
 		statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000))
 		statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000))
-		statedb.Commit(0, true, false)
+		statedb.Commit(0, true)
 
 		chain := &testBlockChain{
 			config:  testChainConfig,
@@ -1305,7 +1305,7 @@ func TestAdd(t *testing.T) {
 				store.Put(blob)
 			}
 		}
-		statedb.Commit(0, true, false)
+		statedb.Commit(0, true)
 		store.Close()
 
 		// Create a blob pool out of the pre-seeded dats
@@ -1378,7 +1378,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
 		statedb.AddBalance(addr, uint256.NewInt(1_000_000_000))
 		pool.add(tx)
 	}
-	statedb.Commit(0, true, false)
+	statedb.Commit(0, true)
 	defer pool.Close()
 
 	// Benchmark assembling the pending
diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go
index 5ff163664e..a0fa2eaa5a 100644
--- a/eth/api_debug_test.go
+++ b/eth/api_debug_test.go
@@ -93,7 +93,7 @@ func TestAccountRange(t *testing.T) {
 			m[addr] = true
 		}
 	}
-	root, _ := sdb.Commit(0, true, false)
+	root, _ := sdb.Commit(0, true)
 	sdb, _ = state.New(root, statedb, nil)
 
 	trie, err := statedb.OpenTrie(root)
@@ -151,7 +151,7 @@ func TestEmptyAccountRange(t *testing.T) {
 		st, _   = state.New(types.EmptyRootHash, statedb, nil)
 	)
 	// Commit(although nothing to flush) and re-init the statedb
-	st.Commit(0, true, false)
+	st.Commit(0, true)
 	st, _ = state.New(types.EmptyRootHash, statedb, nil)
 
 	results := st.RawDump(&state.DumpConfig{
@@ -197,7 +197,7 @@ func TestStorageRangeAt(t *testing.T) {
 	for _, entry := range storage {
 		sdb.SetState(addr, *entry.Key, entry.Value)
 	}
-	root, _ := sdb.Commit(0, false, false)
+	root, _ := sdb.Commit(0, false)
 	sdb, _ = state.New(root, db, nil)
 
 	// Check a few combinations of limit and start/end.
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 15b14b559a..d5f87ce2ef 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -163,7 +163,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
 			return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
 		}
 		// Finalize the state so any modifications are written to the trie
-		root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number()), true)
+		root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number()))
 		if err != nil {
 			return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w",
 				current.NumberU64(), current.Root().Hex(), err)
@@ -172,8 +172,9 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
 		if err != nil {
 			return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err)
 		}
-		// Note: In subnet-evm, the state reference is held by passing true to [statedb.Commit].
-		// Drop the parent state to prevent accumulating too many nodes in memory.
+		// Hold the state reference and also drop the parent state
+		// to prevent accumulating too many nodes in memory.
+		tdb.Reference(root, common.Hash{})
 		if parent != (common.Hash{}) {
 			tdb.Dereference(parent)
 		}
diff --git a/go.mod b/go.mod
index 75608b44bb..8bb078c376 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.22.8
 
 require (
 	github.com/VictoriaMetrics/fastcache v1.12.1
-	github.com/ava-labs/avalanchego v1.11.12-rc.3
+	github.com/ava-labs/avalanchego v1.12.0-initial-poc.9
 	github.com/cespare/cp v0.1.0
 	github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
 	github.com/davecgh/go-spew v1.1.1
@@ -33,16 +33,16 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.12.0
 	github.com/status-im/keycard-go v0.2.0
-	github.com/stretchr/testify v1.8.4
+	github.com/stretchr/testify v1.9.0
 	github.com/tyler-smith/go-bip39 v1.1.0
 	github.com/urfave/cli/v2 v2.25.7
 	go.uber.org/goleak v1.3.0
 	go.uber.org/mock v0.4.0
-	golang.org/x/crypto v0.22.0
+	golang.org/x/crypto v0.26.0
 	golang.org/x/exp v0.0.0-20231127185646-65229373498e
-	golang.org/x/sync v0.7.0
-	golang.org/x/sys v0.19.0
-	golang.org/x/text v0.14.0
+	golang.org/x/sync v0.8.0
+	golang.org/x/sys v0.24.0
+	golang.org/x/text v0.17.0
 	golang.org/x/time v0.3.0
 	google.golang.org/protobuf v1.34.2
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
@@ -55,7 +55,7 @@ require (
 	github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
 	github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
 	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
-	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/cockroachdb/errors v1.9.1 // indirect
 	github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
 	github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect
@@ -99,12 +99,12 @@ require (
 	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.10.1 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
-	github.com/rogpeppe/go-internal v1.10.0 // indirect
+	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/spf13/afero v1.8.2 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/subosito/gotenv v1.3.0 // indirect
-	github.com/supranational/blst v0.3.11 // indirect
+	github.com/supranational/blst v0.3.13 // indirect
 	github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
 	github.com/tklauser/go-sysconf v0.3.12 // indirect
 	github.com/tklauser/numcpus v0.6.1 // indirect
@@ -120,12 +120,12 @@ require (
 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.26.0 // indirect
-	golang.org/x/net v0.24.0 // indirect
-	golang.org/x/term v0.19.0 // indirect
+	golang.org/x/net v0.28.0 // indirect
+	golang.org/x/term v0.23.0 // indirect
 	gonum.org/v1/gonum v0.11.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
-	google.golang.org/grpc v1.62.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect
+	google.golang.org/grpc v1.66.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index e0b4fed0f8..644f445ac7 100644
--- a/go.sum
+++ b/go.sum
@@ -54,8 +54,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/ava-labs/avalanchego v1.11.12-rc.3 h1:cfJ9HCCunCZn922uIfnsw1UKt/c4pOl2/6w/Y7Fwn9Q=
-github.com/ava-labs/avalanchego v1.11.12-rc.3/go.mod h1:qSHmog3wMVjo/ruIAQo0ppXAilyni07NIu5K88RyhWE=
+github.com/ava-labs/avalanchego v1.12.0-initial-poc.9 h1:dQhb+KlPoud+AkRV3A0suKCTodlUSzflGcZElESeVKo=
+github.com/ava-labs/avalanchego v1.12.0-initial-poc.9/go.mod h1:86tO6F1FT8emclUwdQ2WCwAtAerqjm5A4IbV6XxNUyM=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -90,8 +90,9 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
 github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -219,8 +220,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
 github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
-github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
+github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -467,8 +468,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -510,12 +511,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
 github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
-github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
-github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
+github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk=
+github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
 github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
 github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
@@ -600,8 +601,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -684,8 +685,8 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -707,8 +708,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -775,12 +776,12 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
-golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
-golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
+golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
+golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -791,8 +792,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -925,12 +926,10 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
-google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
-google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
-google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
-google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
+google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
+google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
 google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -949,8 +948,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
-google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
+google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
+google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/plugin/evm/block.go b/plugin/evm/block.go
index 4b6ac6824a..a8d9084464 100644
--- a/plugin/evm/block.go
+++ b/plugin/evm/block.go
@@ -148,12 +148,9 @@ func (b *Block) Accept(context.Context) error {
 
 	// Call Accept for relevant precompile logs. Note we do this prior to
 	// calling Accept on the blockChain so any side effects (eg warp signatures)
-	// take place before the accepted log is emitted to subscribers. Use of the
-	// sharedMemoryWriter ensures shared memory requests generated by
-	// precompiles are committed atomically with the vm's lastAcceptedKey.
+	// take place before the accepted log is emitted to subscribers.
 	rules := b.vm.chainConfig.Rules(b.ethBlock.Number(), b.ethBlock.Timestamp())
-	sharedMemoryWriter := NewSharedMemoryWriter()
-	if err := b.handlePrecompileAccept(rules, sharedMemoryWriter); err != nil {
+	if err := b.handlePrecompileAccept(rules); err != nil {
 		return err
 	}
 	if err := vm.blockChain.Accept(b.ethBlock); err != nil {
@@ -177,23 +174,20 @@ func (b *Block) Accept(context.Context) error {
 		return err
 	}
 	// Get pending operations on the vm's versionDB so we can apply them atomically
-	// with the shared memory requests.
+	// with the shared memory changes.
 	vdbBatch, err := b.vm.db.CommitBatch()
 	if err != nil {
 		return fmt.Errorf("could not create commit batch processing block[%s]: %w", b.ID(), err)
 	}
 
-	// Apply any shared memory requests that accumulated from processing the logs
-	// of the accepted block (generated by precompiles) atomically with other pending
-	// changes to the vm's versionDB.
-	return atomicState.Accept(vdbBatch, sharedMemoryWriter.requests)
+	// Apply any shared memory changes atomically with other pending changes to
+	// the vm's versionDB.
+	return atomicState.Accept(vdbBatch, nil)
 }
 
 // handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements
 // contract.Accepter
-// This function assumes that the Accept function will ONLY operate on state maintained in the VM's versiondb.
-// This ensures that any DB operations are performed atomically with marking the block as accepted.
-func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *sharedMemoryWriter) error {
+func (b *Block) handlePrecompileAccept(rules params.Rules) error {
 	// Short circuit early if there are no precompile accepters to execute
 	if len(rules.AccepterPrecompiles) == 0 {
 		return nil
@@ -207,9 +201,8 @@ func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *s
 		return fmt.Errorf("failed to fetch receipts for accepted block with non-empty root hash (%s) (Block: %s, Height: %d)", b.ethBlock.ReceiptHash(), b.ethBlock.Hash(), b.ethBlock.NumberU64())
 	}
 	acceptCtx := &precompileconfig.AcceptContext{
-		SnowCtx:      b.vm.ctx,
-		SharedMemory: sharedMemoryWriter,
-		Warp:         b.vm.warpBackend,
+		SnowCtx: b.vm.ctx,
+		Warp:    b.vm.warpBackend,
 	}
 	for _, receipt := range receipts {
 		for logIdx, log := range receipt.Logs {
@@ -334,7 +327,7 @@ func (b *Block) verify(predicateContext *precompileconfig.PredicateContext, writ
 	// If the chain is still bootstrapping, we can assume that all blocks we are verifying have
 	// been accepted by the network (so the predicate was validated by the network when the
 	// block was originally verified).
-	if b.vm.bootstrapped {
+	if b.vm.bootstrapped.Get() {
 		if err := b.verifyPredicates(predicateContext); err != nil {
 			return fmt.Errorf("failed to verify predicates: %w", err)
 		}
@@ -405,7 +398,7 @@ func (b *Block) verifyUTXOsPresent() error {
 		return nil
 	}
 
-	if !b.vm.bootstrapped {
+	if !b.vm.bootstrapped.Get() {
 		return nil
 	}
 
diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go
index 2db67a6f4b..8ed7aee3cf 100644
--- a/plugin/evm/gossip_test.go
+++ b/plugin/evm/gossip_test.go
@@ -189,7 +189,7 @@ func TestGossipSubscribe(t *testing.T) {
 			defer gossipTxPool.lock.RUnlock()
 
 			for i, tx := range ethTxs {
-				require.Truef(gossipTxPool.bloom.Has(&GossipEthTx{Tx: tx}), "expected tx[%d] to be in bloom filter", i)
+				assert.Truef(c, gossipTxPool.bloom.Has(&GossipEthTx{Tx: tx}), "expected tx[%d] to be in bloom filter", i)
 			}
 		},
 		30*time.Second,
diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go
index b67b834673..b447a717ee 100644
--- a/plugin/evm/import_tx.go
+++ b/plugin/evm/import_tx.go
@@ -221,7 +221,7 @@ func (utx *UnsignedImportTx) SemanticVerify(
 		return fmt.Errorf("import tx contained mismatched number of inputs/credentials (%d vs. %d)", len(utx.ImportedInputs), len(stx.Creds))
 	}
 
-	if !vm.bootstrapped {
+	if !vm.bootstrapped.Get() {
 		// Allow for force committing during bootstrapping
 		return nil
 	}
diff --git a/plugin/evm/shared_memory_writer.go b/plugin/evm/shared_memory_writer.go
deleted file mode 100644
index 7e6de6f862..0000000000
--- a/plugin/evm/shared_memory_writer.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// (c) 2023, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package evm
-
-import (
-	"github.com/ava-labs/avalanchego/chains/atomic"
-	"github.com/ava-labs/avalanchego/ids"
-	"github.com/ava-labs/coreth/precompile/precompileconfig"
-)
-
-var _ precompileconfig.SharedMemoryWriter = &sharedMemoryWriter{}
-
-type sharedMemoryWriter struct {
-	requests map[ids.ID]*atomic.Requests
-}
-
-func NewSharedMemoryWriter() *sharedMemoryWriter {
-	return &sharedMemoryWriter{
-		requests: make(map[ids.ID]*atomic.Requests),
-	}
-}
-
-func (s *sharedMemoryWriter) AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) {
-	mergeAtomicOpsToMap(s.requests, chainID, requests)
-}
-
-// mergeAtomicOps merges atomic ops for [chainID] represented by [requests]
-// to the [output] map provided.
-func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) {
-	if request, exists := output[chainID]; exists {
-		request.PutRequests = append(request.PutRequests, requests.PutRequests...)
-		request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...)
-	} else {
-		output[chainID] = requests
-	}
-}
diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go
index 3ecfcb517c..bba4663153 100644
--- a/plugin/evm/syncervm_test.go
+++ b/plugin/evm/syncervm_test.go
@@ -554,7 +554,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) {
 
 	// check we can transition to [NormalOp] state and continue to process blocks.
 	require.NoError(syncerVM.SetState(context.Background(), snow.NormalOp))
-	require.True(syncerVM.bootstrapped)
+	require.True(syncerVM.bootstrapped.Get())
 
 	// check atomic memory was synced properly
 	syncerSharedMemories := newSharedMemories(syncerAtomicMemory, syncerVM.ctx.ChainID, syncerVM.ctx.XChainID)
diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go
index 00f7de4bce..9361f71976 100644
--- a/plugin/evm/tx.go
+++ b/plugin/evm/tx.go
@@ -288,3 +288,14 @@ func mergeAtomicOps(txs []*Tx) (map[ids.ID]*atomic.Requests, error) {
 	}
 	return output, nil
 }
+
+// mergeAtomicOps merges atomic ops for [chainID] represented by [requests]
+// to the [output] map provided.
+func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) {
+	if request, exists := output[chainID]; exists {
+		request.PutRequests = append(request.PutRequests, requests.PutRequests...)
+		request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...)
+	} else {
+		output[chainID] = requests
+	}
+}
diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go
index 6024747180..af544e9415 100644
--- a/plugin/evm/tx_gossip_test.go
+++ b/plugin/evm/tx_gossip_test.go
@@ -21,7 +21,6 @@ import (
 	"github.com/ava-labs/avalanchego/snow/engine/common"
 	"github.com/ava-labs/avalanchego/snow/engine/enginetest"
 	"github.com/ava-labs/avalanchego/snow/validators"
-	"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
 	agoUtils "github.com/ava-labs/avalanchego/utils"
 	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
 	"github.com/ava-labs/avalanchego/utils/logging"
@@ -43,7 +42,7 @@ func TestEthTxGossip(t *testing.T) {
 	require := require.New(t)
 	ctx := context.Background()
 	snowCtx := utils.TestSnowContext()
-	validatorState := &validatorstest.State{}
+	validatorState := utils.NewTestValidatorState()
 	snowCtx.ValidatorState = validatorState
 
 	pk, err := secp256k1.NewPrivateKey()
@@ -169,15 +168,10 @@ func TestAtomicTxGossip(t *testing.T) {
 	ctx := context.Background()
 	snowCtx := utils.TestSnowContext()
 	snowCtx.AVAXAssetID = ids.GenerateTestID()
-	snowCtx.XChainID = ids.GenerateTestID()
-	validatorState := &validatorstest.State{
-		GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) {
-			return ids.Empty, nil
-		},
-	}
+	validatorState := utils.NewTestValidatorState()
 	snowCtx.ValidatorState = validatorState
 	memory := atomic.NewMemory(memdb.New())
-	snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty)
+	snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID)
 
 	pk, err := secp256k1.NewPrivateKey()
 	require.NoError(err)
@@ -308,14 +302,6 @@ func TestEthTxPushGossipOutbound(t *testing.T) {
 	require := require.New(t)
 	ctx := context.Background()
 	snowCtx := utils.TestSnowContext()
-	snowCtx.ValidatorState = &validatorstest.State{
-		GetCurrentHeightF: func(context.Context) (uint64, error) {
-			return 0, nil
-		},
-		GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
-			return nil, nil
-		},
-	}
 	sender := &enginetest.SenderStub{
 		SentAppGossip: make(chan []byte, 1),
 	}
@@ -440,15 +426,10 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) {
 	ctx := context.Background()
 	snowCtx := utils.TestSnowContext()
 	snowCtx.AVAXAssetID = ids.GenerateTestID()
-	snowCtx.XChainID = ids.GenerateTestID()
-	validatorState := &validatorstest.State{
-		GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) {
-			return ids.Empty, nil
-		},
-	}
+	validatorState := utils.NewTestValidatorState()
 	snowCtx.ValidatorState = validatorState
 	memory := atomic.NewMemory(memdb.New())
-	snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty)
+	snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID)
 
 	pk, err := secp256k1.NewPrivateKey()
 	require.NoError(err)
@@ -518,15 +499,10 @@ func TestAtomicTxPushGossipInbound(t *testing.T) {
 	ctx := context.Background()
 	snowCtx := utils.TestSnowContext()
 	snowCtx.AVAXAssetID = ids.GenerateTestID()
-	snowCtx.XChainID = ids.GenerateTestID()
-	validatorState := &validatorstest.State{
-		GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) {
-			return ids.Empty, nil
-		},
-	}
+	validatorState := utils.NewTestValidatorState()
 	snowCtx.ValidatorState = validatorState
 	memory := atomic.NewMemory(memdb.New())
-	snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty)
+	snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID)
 
 	pk, err := secp256k1.NewPrivateKey()
 	require.NoError(err)
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index e113252e05..50606b0af7 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -17,7 +17,9 @@ import (
 	"sync"
 	"time"
 
+	"github.com/ava-labs/avalanchego/cache/metercacher"
 	"github.com/ava-labs/avalanchego/network/p2p"
+	"github.com/ava-labs/avalanchego/network/p2p/acp118"
 	"github.com/ava-labs/avalanchego/network/p2p/gossip"
 	"github.com/ava-labs/avalanchego/upgrade"
 	avalanchegoConstants "github.com/ava-labs/avalanchego/utils/constants"
@@ -49,7 +51,6 @@ import (
 	statesyncclient "github.com/ava-labs/coreth/sync/client"
 	"github.com/ava-labs/coreth/sync/client/stats"
 	"github.com/ava-labs/coreth/warp"
-	"github.com/ava-labs/coreth/warp/handlers"
 
 	// Force-load tracer engine to trigger registration
 	//
@@ -316,7 +317,7 @@ type VM struct {
 	// Metrics
 	sdkMetrics *prometheus.Registry
 
-	bootstrapped bool
+	bootstrapped avalancheUtils.Atomic[bool]
 	IsPlugin     bool
 
 	logger CorethLogger
@@ -336,6 +337,10 @@ type VM struct {
 	atomicTxGossipHandler p2p.Handler
 	atomicTxPushGossiper  *gossip.PushGossiper[*GossipAtomicTx]
 	atomicTxPullGossiper  gossip.Gossiper
+
+	chainAlias string
+	// RPC handlers (should be stopped before closing chaindb)
+	rpcHandlers []interface{ Stop() }
 }
 
 // CodecRegistry implements the secp256k1fx interface
@@ -392,13 +397,14 @@ func (vm *VM) Initialize(
 		// fallback to ChainID string instead of erroring
 		alias = vm.ctx.ChainID.String()
 	}
+	vm.chainAlias = alias
 
 	var writer io.Writer = vm.ctx.Log
 	if vm.IsPlugin {
 		writer = originalStderr
 	}
 
-	corethLogger, err := InitLogger(alias, vm.config.LogLevel, vm.config.LogJSONFormat, writer)
+	corethLogger, err := InitLogger(vm.chainAlias, vm.config.LogLevel, vm.config.LogJSONFormat, writer)
 	if err != nil {
 		return fmt.Errorf("failed to initialize logger due to: %w ", err)
 	}
@@ -419,16 +425,15 @@ func (vm *VM) Initialize(
 
 	vm.toEngine = toEngine
 	vm.shutdownChan = make(chan struct{}, 1)
-	// Use NewNested rather than New so that the structure of the database
-	// remains the same regardless of the provided baseDB type.
-	vm.chaindb = rawdb.NewDatabase(Database{prefixdb.NewNested(ethDBPrefix, db)})
-	vm.db = versiondb.New(db)
-	vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db)
-	vm.metadataDB = prefixdb.New(metadataPrefix, vm.db)
-	// Note warpDB is not part of versiondb because it is not necessary
-	// that warp signatures are committed to the database atomically with
-	// the last accepted block.
-	vm.warpDB = prefixdb.New(warpPrefix, db)
+
+	if err := vm.initializeMetrics(); err != nil {
+		return fmt.Errorf("failed to initialize metrics: %w", err)
+	}
+
+	// Initialize the database
+	if err := vm.initializeDBs(db); err != nil {
+		return fmt.Errorf("failed to initialize databases: %w", err)
+	}
 
 	if vm.config.InspectDatabase {
 		start := time.Now()
@@ -571,10 +576,6 @@ func (vm *VM) Initialize(
 
 	vm.codec = Codec
 
-	if err := vm.initializeMetrics(); err != nil {
-		return err
-	}
-
 	// TODO: read size from settings
 	vm.mempool, err = NewMempool(chainCtx, vm.sdkMetrics, defaultMempoolSize, vm.verifyTxAtTip)
 	if err != nil {
@@ -600,26 +601,31 @@ func (vm *VM) Initialize(
 	for i, hexMsg := range vm.config.WarpOffChainMessages {
 		offchainWarpMessages[i] = []byte(hexMsg)
 	}
+	warpSignatureCache := &cache.LRU[ids.ID, []byte]{Size: warpSignatureCacheSize}
+	meteredCache, err := metercacher.New("warp_signature_cache", vm.sdkMetrics, warpSignatureCache)
+	if err != nil {
+		return fmt.Errorf("failed to create warp signature cache: %w", err)
+	}
+
+	// clear warpdb on initialization if config enabled
+	if vm.config.PruneWarpDB {
+		if err := database.Clear(vm.warpDB, ethdb.IdealBatchSize); err != nil {
+			return fmt.Errorf("failed to prune warpDB: %w", err)
+		}
+	}
+
 	vm.warpBackend, err = warp.NewBackend(
 		vm.ctx.NetworkID,
 		vm.ctx.ChainID,
 		vm.ctx.WarpSigner,
 		vm,
 		vm.warpDB,
-		warpSignatureCacheSize,
+		meteredCache,
 		offchainWarpMessages,
 	)
 	if err != nil {
 		return err
 	}
-
-	// clear warpdb on initialization if config enabled
-	if vm.config.PruneWarpDB {
-		if err := vm.warpBackend.Clear(); err != nil {
-			return fmt.Errorf("failed to prune warpDB: %w", err)
-		}
-	}
-
 	if err := vm.initializeChain(lastAcceptedHash); err != nil {
 		return err
 	}
@@ -660,7 +666,17 @@ func (vm *VM) Initialize(
 		return err
 	}
 
-	vm.initializeHandlers()
+	// Add p2p warp message warpHandler
+	warpHandler := acp118.NewCachedHandler(meteredCache, vm.warpBackend, vm.ctx.WarpSigner)
+	vm.Network.AddHandler(p2p.SignatureRequestHandlerID, warpHandler)
+
+	vm.setAppRequestHandlers()
+
+	vm.StateSyncServer = NewStateSyncServer(&stateSyncServerConfig{
+		Chain:            vm.blockChain,
+		AtomicTrie:       vm.atomicTrie,
+		SyncableInterval: vm.config.StateSyncCommitInterval,
+	})
 	return vm.initializeStateSyncClient(lastAcceptedHeight)
 }
 
@@ -799,21 +815,6 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error {
 	return nil
 }
 
-// initializeHandlers should be called after [vm.chain] is initialized.
-func (vm *VM) initializeHandlers() {
-	vm.StateSyncServer = NewStateSyncServer(&stateSyncServerConfig{
-		Chain:            vm.blockChain,
-		AtomicTrie:       vm.atomicTrie,
-		SyncableInterval: vm.config.StateSyncCommitInterval,
-	})
-
-	// Add p2p warp message warpHandler
-	warpHandler := handlers.NewSignatureRequestHandlerP2P(vm.warpBackend, vm.networkCodec)
-	vm.Network.AddHandler(p2p.SignatureRequestHandlerID, warpHandler)
-
-	vm.setAppRequestHandlers()
-}
-
 func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error {
 	block, err := vm.newBlock(lastAcceptedBlock)
 	if err != nil {
@@ -1076,33 +1077,48 @@ func (vm *VM) onExtraStateChange(block *types.Block, state *state.StateDB) (*big
 func (vm *VM) SetState(_ context.Context, state snow.State) error {
 	switch state {
 	case snow.StateSyncing:
-		vm.bootstrapped = false
+		vm.bootstrapped.Set(false)
 		return nil
 	case snow.Bootstrapping:
-		vm.bootstrapped = false
-		if err := vm.StateSyncClient.Error(); err != nil {
-			return err
-		}
-		// After starting bootstrapping, do not attempt to resume a previous state sync.
-		if err := vm.StateSyncClient.ClearOngoingSummary(); err != nil {
-			return err
-		}
-		// Ensure snapshots are initialized before bootstrapping (i.e., if state sync is skipped).
-		// Note calling this function has no effect if snapshots are already initialized.
-		vm.blockChain.InitializeSnapshots()
-		return vm.fx.Bootstrapping()
+		return vm.onBootstrapStarted()
 	case snow.NormalOp:
-		// Initialize goroutines related to block building once we enter normal operation as there is no need to handle mempool gossip before this point.
-		if err := vm.initBlockBuilding(); err != nil {
-			return fmt.Errorf("failed to initialize block building: %w", err)
-		}
-		vm.bootstrapped = true
-		return vm.fx.Bootstrapped()
+		return vm.onNormalOperationsStarted()
 	default:
 		return snow.ErrUnknownState
 	}
 }
 
+// onBootstrapStarted marks this VM as bootstrapping
+func (vm *VM) onBootstrapStarted() error {
+	vm.bootstrapped.Set(false)
+	if err := vm.StateSyncClient.Error(); err != nil {
+		return err
+	}
+	// After starting bootstrapping, do not attempt to resume a previous state sync.
+	if err := vm.StateSyncClient.ClearOngoingSummary(); err != nil {
+		return err
+	}
+	// Ensure snapshots are initialized before bootstrapping (i.e., if state sync is skipped).
+	// Note calling this function has no effect if snapshots are already initialized.
+	vm.blockChain.InitializeSnapshots()
+
+	return vm.fx.Bootstrapping()
+}
+
+// onNormalOperationsStarted marks this VM as bootstrapped
+func (vm *VM) onNormalOperationsStarted() error {
+	if vm.bootstrapped.Get() {
+		return nil
+	}
+	vm.bootstrapped.Set(true)
+	if err := vm.fx.Bootstrapped(); err != nil {
+		return err
+	}
+	// Initialize goroutines related to block building
+	// once we enter normal operation as there is no need to handle mempool gossip before this point.
+	return vm.initBlockBuilding()
+}
+
 // initBlockBuilding starts goroutines to manage block building
 func (vm *VM) initBlockBuilding() error {
 	ctx, cancel := context.WithCancel(context.TODO())
@@ -1116,7 +1132,7 @@ func (vm *VM) initBlockBuilding() error {
 	}
 	ethTxPool, err := NewGossipEthTxPool(vm.txPool, vm.sdkMetrics)
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to initialize gossip eth tx pool: %w", err)
 	}
 	vm.shutdownWg.Add(1)
 	go func() {
@@ -1199,7 +1215,7 @@ func (vm *VM) initBlockBuilding() error {
 	}
 
 	if err := vm.Network.AddHandler(p2p.TxGossipHandlerID, vm.ethTxGossipHandler); err != nil {
-		return err
+		return fmt.Errorf("failed to add eth tx gossip handler: %w", err)
 	}
 
 	if vm.atomicTxGossipHandler == nil {
@@ -1216,7 +1232,7 @@ func (vm *VM) initBlockBuilding() error {
 	}
 
 	if err := vm.Network.AddHandler(p2p.AtomicTxGossipHandlerID, vm.atomicTxGossipHandler); err != nil {
-		return err
+		return fmt.Errorf("failed to add atomic tx gossip handler: %w", err)
 	}
 
 	if vm.ethTxPullGossiper == nil {
@@ -1279,8 +1295,8 @@ func (vm *VM) initBlockBuilding() error {
 // setAppRequestHandlers sets the request handlers for the VM to serve state sync
 // requests.
 func (vm *VM) setAppRequestHandlers() {
-	// Create separate EVM TrieDB (read only) for serving leafs requests.
-	// We create a separate TrieDB here, so that it has a separate cache from the one
+	// Create standalone EVM TrieDB (read only) for serving leafs requests.
+	// We create a standalone TrieDB here, so that it has a standalone cache from the one
 	// used by the node when processing blocks.
 	evmTrieDB := triedb.NewDatabase(
 		vm.chaindb,
@@ -1314,6 +1330,10 @@ func (vm *VM) Shutdown(context.Context) error {
 		log.Error("error stopping state syncer", "err", err)
 	}
 	close(vm.shutdownChan)
+	// Stop RPC handlers before eth.Stop which will close the database
+	for _, handler := range vm.rpcHandlers {
+		handler.Stop()
+	}
 	vm.eth.Stop()
 	vm.shutdownWg.Wait()
 	return nil
@@ -1500,10 +1520,6 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
 		return nil, err
 	}
 
-	primaryAlias, err := vm.ctx.BCLookup.PrimaryAlias(vm.ctx.ChainID)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get primary alias for chain due to %w", err)
-	}
 	apis := make(map[string]http.Handler)
 	avaxAPI, err := newHandler("avax", &AvaxAPI{vm})
 	if err != nil {
@@ -1513,7 +1529,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
 	apis[avaxEndpoint] = avaxAPI
 
 	if vm.config.AdminAPIEnabled {
-		adminAPI, err := newHandler("admin", NewAdminService(vm, os.ExpandEnv(fmt.Sprintf("%s_coreth_performance_%s", vm.config.AdminAPIDir, primaryAlias))))
+		adminAPI, err := newHandler("admin", NewAdminService(vm, os.ExpandEnv(fmt.Sprintf("%s_coreth_performance_%s", vm.config.AdminAPIDir, vm.chainAlias))))
 		if err != nil {
 			return nil, fmt.Errorf("failed to register service for admin API due to %w", err)
 		}
@@ -1544,9 +1560,26 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
 		vm.config.WSCPUMaxStored.Duration,
 	)
 
+	vm.rpcHandlers = append(vm.rpcHandlers, handler)
 	return apis, nil
 }
 
+// initializeDBs initializes the databases used by the VM.
+// coreth always uses the avalanchego provided database.
+func (vm *VM) initializeDBs(db database.Database) error {
+	// Use NewNested rather than New so that the structure of the database
+	// remains the same regardless of the provided baseDB type.
+	vm.chaindb = rawdb.NewDatabase(Database{prefixdb.NewNested(ethDBPrefix, db)})
+	vm.db = versiondb.New(db)
+	vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db)
+	vm.metadataDB = prefixdb.New(metadataPrefix, vm.db)
+	// Note warpDB is not part of versiondb because it is not necessary
+	// that warp signatures are committed to the database atomically with
+	// the last accepted block.
+	vm.warpDB = prefixdb.New(warpPrefix, db)
+	return nil
+}
+
 // CreateStaticHandlers makes new http handlers that can handle API calls
 func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) {
 	handler := rpc.NewServer(0)
@@ -1557,6 +1590,7 @@ func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, er
 		return nil, err
 	}
 
+	vm.rpcHandlers = append(vm.rpcHandlers, handler)
 	return map[string]http.Handler{
 		"/rpc": handler,
 	}, nil
diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go
index ec333e4132..7893588ac2 100644
--- a/plugin/evm/vm_warp_test.go
+++ b/plugin/evm/vm_warp_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/ava-labs/avalanchego/ids"
 	commonEng "github.com/ava-labs/avalanchego/snow/engine/common"
+	"github.com/ava-labs/avalanchego/snow/engine/enginetest"
 	"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
 	"github.com/ava-labs/avalanchego/snow/validators"
 	"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
@@ -31,9 +32,10 @@ import (
 	"github.com/ava-labs/coreth/params"
 	"github.com/ava-labs/coreth/plugin/evm/message"
 	"github.com/ava-labs/coreth/precompile/contract"
-	"github.com/ava-labs/coreth/precompile/contracts/warp"
+	warpcontract "github.com/ava-labs/coreth/precompile/contracts/warp"
 	"github.com/ava-labs/coreth/predicate"
 	"github.com/ava-labs/coreth/utils"
+	"github.com/ava-labs/coreth/warp"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/stretchr/testify/require"
@@ -74,7 +76,7 @@ func TestSendWarpMessage(t *testing.T) {
 
 	payloadData := avagoUtils.RandomBytes(100)
 
-	warpSendMessageInput, err := warp.PackSendWarpMessage(payloadData)
+	warpSendMessageInput, err := warpcontract.PackSendWarpMessage(payloadData)
 	require.NoError(err)
 	addressedPayload, err := payload.NewAddressedCall(
 		testEthAddrs[0].Bytes(),
@@ -89,7 +91,7 @@ func TestSendWarpMessage(t *testing.T) {
 	require.NoError(err)
 
 	// Submit a transaction to trigger sending a warp message
-	tx0 := types.NewTransaction(uint64(0), warp.ContractAddress, big.NewInt(1), 100_000, big.NewInt(params.LaunchMinGasPrice), warpSendMessageInput)
+	tx0 := types.NewTransaction(uint64(0), warpcontract.ContractAddress, big.NewInt(1), 100_000, big.NewInt(params.LaunchMinGasPrice), warpSendMessageInput)
 	signedTx0, err := types.SignTx(tx0, types.LatestSignerForChainID(vm.chainConfig.ChainID), testKeys[0].ToECDSA())
 	require.NoError(err)
 
@@ -110,20 +112,19 @@ func TestSendWarpMessage(t *testing.T) {
 
 	require.Len(receipts[0].Logs, 1)
 	expectedTopics := []common.Hash{
-		warp.WarpABI.Events["SendWarpMessage"].ID,
+		warpcontract.WarpABI.Events["SendWarpMessage"].ID,
 		common.BytesToHash(testEthAddrs[0].Bytes()),
 		common.Hash(expectedUnsignedMessage.ID()),
 	}
 	require.Equal(expectedTopics, receipts[0].Logs[0].Topics)
 	logData := receipts[0].Logs[0].Data
-	unsignedMessage, err := warp.UnpackSendWarpEventDataToMessage(logData)
+	unsignedMessage, err := warpcontract.UnpackSendWarpEventDataToMessage(logData)
 	require.NoError(err)
-	unsignedMessageID := unsignedMessage.ID()
 
 	// Verify the signature cannot be fetched before the block is accepted
-	_, err = vm.warpBackend.GetMessageSignature(unsignedMessageID)
+	_, err = vm.warpBackend.GetMessageSignature(context.TODO(), unsignedMessage)
 	require.Error(err)
-	_, err = vm.warpBackend.GetBlockSignature(blk.ID())
+	_, err = vm.warpBackend.GetBlockSignature(context.TODO(), blk.ID())
 	require.Error(err)
 
 	require.NoError(vm.SetPreference(context.Background(), blk.ID()))
@@ -131,7 +132,7 @@ func TestSendWarpMessage(t *testing.T) {
 	vm.blockChain.DrainAcceptorQueue()
 
 	// Verify the message signature after accepting the block.
-	rawSignatureBytes, err := vm.warpBackend.GetMessageSignature(unsignedMessageID)
+	rawSignatureBytes, err := vm.warpBackend.GetMessageSignature(context.TODO(), unsignedMessage)
 	require.NoError(err)
 	blsSignature, err := bls.SignatureFromBytes(rawSignatureBytes[:])
 	require.NoError(err)
@@ -148,7 +149,7 @@ func TestSendWarpMessage(t *testing.T) {
 	require.True(bls.Verify(vm.ctx.PublicKey, blsSignature, unsignedMessage.Bytes()))
 
 	// Verify the blockID will now be signed by the backend and produces a valid signature.
-	rawSignatureBytes, err = vm.warpBackend.GetBlockSignature(blk.ID())
+	rawSignatureBytes, err = vm.warpBackend.GetBlockSignature(context.TODO(), blk.ID())
 	require.NoError(err)
 	blsSignature, err = bls.SignatureFromBytes(rawSignatureBytes[:])
 	require.NoError(err)
@@ -341,7 +342,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned
 			common.Big0,
 			txPayload,
 			types.AccessList{},
-			warp.ContractAddress,
+			warpcontract.ContractAddress,
 			signedMessage.Bytes(),
 		),
 		types.LatestSignerForChainID(vm.chainConfig.ChainID),
@@ -410,15 +411,15 @@ func TestReceiveWarpMessage(t *testing.T) {
 
 	// enable warp at the default genesis time
 	enableTime := upgrade.InitiallyActiveTime
-	enableConfig := warp.NewDefaultConfig(utils.TimeToNewUint64(enableTime))
+	enableConfig := warpcontract.NewDefaultConfig(utils.TimeToNewUint64(enableTime))
 
 	// disable warp so we can re-enable it with RequirePrimaryNetworkSigners
 	disableTime := upgrade.InitiallyActiveTime.Add(10 * time.Second)
-	disableConfig := warp.NewDisableConfig(utils.TimeToNewUint64(disableTime))
+	disableConfig := warpcontract.NewDisableConfig(utils.TimeToNewUint64(disableTime))
 
 	// re-enable warp with RequirePrimaryNetworkSigners
 	reEnableTime := disableTime.Add(10 * time.Second)
-	reEnableConfig := warp.NewConfig(
+	reEnableConfig := warpcontract.NewConfig(
 		utils.TimeToNewUint64(reEnableTime),
 		0,    // QuorumNumerator
 		true, // RequirePrimaryNetworkSigners
@@ -458,7 +459,7 @@ func TestReceiveWarpMessage(t *testing.T) {
 		},
 		{
 			name:          "C-Chain message should be signed by subnet without RequirePrimaryNetworkSigners",
-			sourceChainID: testCChainID,
+			sourceChainID: vm.ctx.CChainID,
 			msgFrom:       fromPrimary,
 			useSigners:    signersSubnet,
 			blockTime:     upgrade.InitiallyActiveTime.Add(2 * blockGap),
@@ -481,7 +482,7 @@ func TestReceiveWarpMessage(t *testing.T) {
 		},
 		{
 			name:          "C-Chain message should be signed by primary with RequirePrimaryNetworkSigners (impacted)",
-			sourceChainID: testCChainID,
+			sourceChainID: vm.ctx.CChainID,
 			msgFrom:       fromPrimary,
 			useSigners:    signersPrimary,
 			blockTime:     reEnableTime.Add(2 * blockGap),
@@ -607,20 +608,20 @@ func testReceiveWarpMessage(
 	)
 	require.NoError(err)
 
-	getWarpMsgInput, err := warp.PackGetVerifiedWarpMessage(0)
+	getWarpMsgInput, err := warpcontract.PackGetVerifiedWarpMessage(0)
 	require.NoError(err)
 	getVerifiedWarpMessageTx, err := types.SignTx(
 		predicate.NewPredicateTx(
 			vm.chainConfig.ChainID,
 			vm.txPool.Nonce(testEthAddrs[0]),
-			&warp.Module.Address,
+			&warpcontract.Module.Address,
 			1_000_000,
 			big.NewInt(225*params.GWei),
 			big.NewInt(params.GWei),
 			common.Big0,
 			getWarpMsgInput,
 			types.AccessList{},
-			warp.ContractAddress,
+			warpcontract.ContractAddress,
 			signedMessage.Bytes(),
 		),
 		types.LatestSignerForChainID(vm.chainConfig.ChainID),
@@ -653,7 +654,7 @@ func testReceiveWarpMessage(
 	// An empty bitset indicates success.
 	txResultsBytes := results.GetResults(
 		getVerifiedWarpMessageTx.Hash(),
-		warp.ContractAddress,
+		warpcontract.ContractAddress,
 	)
 	bitset := set.BitsFromBytes(txResultsBytes)
 	require.Zero(bitset.Len()) // Empty bitset indicates success
@@ -686,8 +687,8 @@ func testReceiveWarpMessage(
 	verifiedMessageTxReceipt := verifiedMessageReceipts[0]
 	require.Equal(types.ReceiptStatusSuccessful, verifiedMessageTxReceipt.Status)
 
-	expectedOutput, err := warp.PackGetVerifiedWarpMessageOutput(warp.GetVerifiedWarpMessageOutput{
-		Message: warp.WarpMessage{
+	expectedOutput, err := warpcontract.PackGetVerifiedWarpMessageOutput(warpcontract.GetVerifiedWarpMessageOutput{
+		Message: warpcontract.WarpMessage{
 			SourceChainID:       common.Hash(sourceChainID),
 			OriginSenderAddress: testEthAddrs[0],
 			Payload:             payloadData,
@@ -728,8 +729,10 @@ func TestMessageSignatureRequestsToVM(t *testing.T) {
 	// Add the known message and get its signature to confirm.
 	err = vm.warpBackend.AddMessage(warpMessage)
 	require.NoError(t, err)
-	signature, err := vm.warpBackend.GetMessageSignature(warpMessage.ID())
+	signature, err := vm.warpBackend.GetMessageSignature(context.TODO(), warpMessage)
 	require.NoError(t, err)
+	var knownSignature [bls.SignatureLen]byte
+	copy(knownSignature[:], signature)
 
 	tests := map[string]struct {
 		messageID        ids.ID
@@ -737,7 +740,7 @@ func TestMessageSignatureRequestsToVM(t *testing.T) {
 	}{
 		"known": {
 			messageID:        warpMessage.ID(),
-			expectedResponse: signature,
+			expectedResponse: knownSignature,
 		},
 		"unknown": {
 			messageID:        ids.GenerateTestID(),
@@ -784,8 +787,10 @@ func TestBlockSignatureRequestsToVM(t *testing.T) {
 	lastAcceptedID, err := vm.LastAccepted(context.Background())
 	require.NoError(t, err)
 
-	signature, err := vm.warpBackend.GetBlockSignature(lastAcceptedID)
+	signature, err := vm.warpBackend.GetBlockSignature(context.TODO(), lastAcceptedID)
 	require.NoError(t, err)
+	var knownSignature [bls.SignatureLen]byte
+	copy(knownSignature[:], signature)
 
 	tests := map[string]struct {
 		blockID          ids.ID
@@ -793,7 +798,7 @@ func TestBlockSignatureRequestsToVM(t *testing.T) {
 	}{
 		"known": {
 			blockID:          lastAcceptedID,
-			expectedResponse: signature,
+			expectedResponse: knownSignature,
 		},
 		"unknown": {
 			blockID:          ids.GenerateTestID(),
@@ -828,3 +833,61 @@ func TestBlockSignatureRequestsToVM(t *testing.T) {
 		})
 	}
 }
+
+func TestClearWarpDB(t *testing.T) {
+	ctx, db, genesisBytes, issuer, _ := setupGenesis(t, genesisJSONLatest)
+	vm := &VM{}
+	err := vm.Initialize(context.Background(), ctx, db, genesisBytes, []byte{}, []byte{}, issuer, []*commonEng.Fx{}, &enginetest.Sender{})
+	require.NoError(t, err)
+
+	// use multiple messages to test that all messages get cleared
+	payloads := [][]byte{[]byte("test1"), []byte("test2"), []byte("test3"), []byte("test4"), []byte("test5")}
+	messages := []*avalancheWarp.UnsignedMessage{}
+
+	// add all messages
+	for _, payload := range payloads {
+		unsignedMsg, err := avalancheWarp.NewUnsignedMessage(vm.ctx.NetworkID, vm.ctx.ChainID, payload)
+		require.NoError(t, err)
+		err = vm.warpBackend.AddMessage(unsignedMsg)
+		require.NoError(t, err)
+		// ensure that the message was added
+		_, err = vm.warpBackend.GetMessageSignature(context.TODO(), unsignedMsg)
+		require.NoError(t, err)
+		messages = append(messages, unsignedMsg)
+	}
+
+	require.NoError(t, vm.Shutdown(context.Background()))
+
+	// Restart VM with the same database default should not prune the warp db
+	vm = &VM{}
+	// we need new context since the previous one has registered metrics.
+	ctx, _, _, _, _ = setupGenesis(t, genesisJSONLatest)
+	err = vm.Initialize(context.Background(), ctx, db, genesisBytes, []byte{}, []byte{}, issuer, []*commonEng.Fx{}, &enginetest.Sender{})
+	require.NoError(t, err)
+
+	// check messages are still present
+	for _, message := range messages {
+		bytes, err := vm.warpBackend.GetMessageSignature(context.TODO(), message)
+		require.NoError(t, err)
+		require.NotEmpty(t, bytes)
+	}
+
+	require.NoError(t, vm.Shutdown(context.Background()))
+
+	// restart the VM with pruning enabled
+	vm = &VM{}
+	config := `{"prune-warp-db-enabled": true}`
+	ctx, _, _, _, _ = setupGenesis(t, genesisJSONLatest)
+	err = vm.Initialize(context.Background(), ctx, db, genesisBytes, []byte{}, []byte(config), issuer, []*commonEng.Fx{}, &enginetest.Sender{})
+	require.NoError(t, err)
+
+	it := vm.warpDB.NewIterator()
+	require.False(t, it.Next())
+	it.Release()
+
+	// ensure all messages have been deleted
+	for _, message := range messages {
+		_, err := vm.warpBackend.GetMessageSignature(context.TODO(), message)
+		require.ErrorIs(t, err, &commonEng.AppError{Code: warp.ParseErrCode})
+	}
+}
diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go
index 43365e3bf9..eb741cbc55 100644
--- a/precompile/precompileconfig/config.go
+++ b/precompile/precompileconfig/config.go
@@ -5,8 +5,6 @@
 package precompileconfig
 
 import (
-	"github.com/ava-labs/avalanchego/chains/atomic"
-	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/snow"
 	"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
 	"github.com/ava-labs/avalanchego/vms/platformvm/warp"
@@ -53,21 +51,14 @@ type Predicater interface {
 	VerifyPredicate(predicateContext *PredicateContext, predicateBytes []byte) error
 }
 
-// SharedMemoryWriter defines an interface to allow a precompile's Accepter to write operations
-// into shared memory to be committed atomically on block accept.
-type SharedMemoryWriter interface {
-	AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests)
-}
-
 type WarpMessageWriter interface {
 	AddMessage(unsignedMessage *warp.UnsignedMessage) error
 }
 
 // AcceptContext defines the context passed in to a precompileconfig's Accepter
 type AcceptContext struct {
-	SnowCtx      *snow.Context
-	SharedMemory SharedMemoryWriter
-	Warp         WarpMessageWriter
+	SnowCtx *snow.Context
+	Warp    WarpMessageWriter
 }
 
 // Accepter is an optional interface for StatefulPrecompiledContracts to implement.
diff --git a/scripts/versions.sh b/scripts/versions.sh
index 2294b628bf..7072589cb1 100644
--- a/scripts/versions.sh
+++ b/scripts/versions.sh
@@ -6,4 +6,4 @@
 set -euo pipefail
 
 # Don't export them as they're used in the context of other calls
-AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.12-rc.3'}
+AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.0-initial-poc.9'}
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index a919bbb725..dc3dcb1ea4 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -66,7 +66,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo
 		}
 	}
 	// Commit and re-open to start with a clean state.
-	root, _ := statedb.Commit(0, false, false)
+	root, _ := statedb.Commit(0, false)
 
 	// If snapshot is requested, initialize the snapshotter and use it in state.
 	var snaps *snapshot.Tree
diff --git a/triedb/database.go b/triedb/database.go
index 295c723bbc..7421b74cf0 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -148,17 +148,6 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n
 	return db.backend.Update(root, parent, block, nodes, states)
 }
 
-func (db *Database) UpdateAndReferenceRoot(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
-	if db.preimages != nil {
-		db.preimages.commit(false)
-	}
-	hdb, ok := db.backend.(*hashdb.Database)
-	if ok {
-		return hdb.UpdateAndReferenceRoot(root, parent, block, nodes, states)
-	}
-	return db.backend.Update(root, parent, block, nodes, states)
-}
-
 // Commit iterates over all the children of a particular node, writes them out
 // to disk. As a side effect, all pre-images accumulated up to this point are
 // also written.
diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go
index f5c55b3198..11efee0fb8 100644
--- a/triedb/hashdb/database.go
+++ b/triedb/hashdb/database.go
@@ -97,8 +97,9 @@ type cache interface {
 
 // Config contains the settings for database.
 type Config struct {
-	CleanCacheSize int    // Maximum memory allowance (in bytes) for caching clean nodes
-	StatsPrefix    string // Prefix for cache stats (disabled if empty)
+	CleanCacheSize                  int    // Maximum memory allowance (in bytes) for caching clean nodes
+	StatsPrefix                     string // Prefix for cache stats (disabled if empty)
+	ReferenceRootAtomicallyOnUpdate bool   // Whether to reference the root node on update
 }
 
 // Defaults is the default setting for database if it's not specified.
@@ -137,6 +138,8 @@ type Database struct {
 	childrenSize common.StorageSize // Storage size of the external children tracking
 
 	lock sync.RWMutex
+
+	referenceRoot bool
 }
 
 // cachedNode is all the information we know about a single cached trie node
@@ -174,10 +177,11 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas
 		cleans = utils.NewMeteredCache(config.CleanCacheSize, config.StatsPrefix, cacheStatsUpdateFrequency)
 	}
 	return &Database{
-		diskdb:   diskdb,
-		resolver: resolver,
-		cleans:   cleans,
-		dirties:  make(map[common.Hash]*cachedNode),
+		diskdb:        diskdb,
+		resolver:      resolver,
+		cleans:        cleans,
+		dirties:       make(map[common.Hash]*cachedNode),
+		referenceRoot: config.ReferenceRootAtomicallyOnUpdate,
 	}
 }
 
@@ -627,6 +631,8 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
 
 // Update inserts the dirty nodes in provided nodeset into database and link the
 // account trie with multiple storage tries if necessary.
+// If ReferenceRootAtomicallyOnUpdate was enabled in the config, it will also add a reference from
+// the root to the metaroot while holding the db's lock.
 func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
 	// Ensure the parent state is present and signal a warning if not.
 	if parent != types.EmptyRootHash {
@@ -637,26 +643,13 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n
 	db.lock.Lock()
 	defer db.lock.Unlock()
 
-	return db.update(root, parent, nodes)
-}
-
-// UpdateAndReferenceRoot inserts the dirty nodes in provided nodeset into
-// database and links the account trie with multiple storage tries if necessary,
-// then adds a reference [from] root to the metaroot while holding the db's lock.
-func (db *Database) UpdateAndReferenceRoot(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
-	// Ensure the parent state is present and signal a warning if not.
-	if parent != types.EmptyRootHash {
-		if blob, _ := db.node(parent); len(blob) == 0 {
-			log.Error("parent state is not present")
-		}
-	}
-	db.lock.Lock()
-	defer db.lock.Unlock()
-
 	if err := db.update(root, parent, nodes); err != nil {
 		return err
 	}
-	db.reference(root, common.Hash{})
+
+	if db.referenceRoot {
+		db.reference(root, common.Hash{})
+	}
 	return nil
 }
 
diff --git a/utils/snow.go b/utils/snow.go
index 77368f0750..e46e2ac7b1 100644
--- a/utils/snow.go
+++ b/utils/snow.go
@@ -4,12 +4,24 @@
 package utils
 
 import (
+	"context"
+	"errors"
+
 	"github.com/ava-labs/avalanchego/api/metrics"
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/snow"
+	"github.com/ava-labs/avalanchego/snow/validators"
 	"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
+	"github.com/ava-labs/avalanchego/utils/constants"
 	"github.com/ava-labs/avalanchego/utils/crypto/bls"
 	"github.com/ava-labs/avalanchego/utils/logging"
+	"github.com/ava-labs/avalanchego/vms/platformvm/warp"
+)
+
+var (
+	testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'}
+	testXChainID = ids.ID{'t', 'e', 's', 't', 'x'}
+	testChainID  = ids.ID{'t', 'e', 's', 't', 'c', 'h', 'a', 'i', 'n'}
 )
 
 func TestSnowContext() *snow.Context {
@@ -18,16 +30,49 @@ func TestSnowContext() *snow.Context {
 		panic(err)
 	}
 	pk := bls.PublicFromSecretKey(sk)
-	return &snow.Context{
-		NetworkID:      0,
+	networkID := constants.UnitTestID
+	chainID := testChainID
+
+	ctx := &snow.Context{
+		NetworkID:      networkID,
 		SubnetID:       ids.Empty,
-		ChainID:        ids.Empty,
-		NodeID:         ids.EmptyNodeID,
+		ChainID:        chainID,
+		NodeID:         ids.GenerateTestNodeID(),
+		XChainID:       testXChainID,
+		CChainID:       testCChainID,
 		PublicKey:      pk,
+		WarpSigner:     warp.NewSigner(sk, networkID, chainID),
 		Log:            logging.NoLog{},
 		BCLookup:       ids.NewAliaser(),
 		Metrics:        metrics.NewPrefixGatherer(),
 		ChainDataDir:   "",
-		ValidatorState: &validatorstest.State{},
+		ValidatorState: NewTestValidatorState(),
+	}
+
+	return ctx
+}
+
+func NewTestValidatorState() *validatorstest.State {
+	return &validatorstest.State{
+		GetCurrentHeightF: func(context.Context) (uint64, error) {
+			return 0, nil
+		},
+		GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) {
+			subnetID, ok := map[ids.ID]ids.ID{
+				constants.PlatformChainID: constants.PrimaryNetworkID,
+				testXChainID:              constants.PrimaryNetworkID,
+				testCChainID:              constants.PrimaryNetworkID,
+			}[chainID]
+			if !ok {
+				return ids.Empty, errors.New("unknown chain")
+			}
+			return subnetID, nil
+		},
+		GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
+			return map[ids.NodeID]*validators.GetValidatorOutput{}, nil
+		},
+		GetCurrentValidatorSetF: func(context.Context, ids.ID) (map[ids.ID]*validators.GetCurrentValidatorOutput, uint64, error) {
+			return map[ids.ID]*validators.GetCurrentValidatorOutput{}, 0, nil
+		},
 	}
 }
diff --git a/warp/backend.go b/warp/backend.go
index 7e7377ad57..6e1f6a9553 100644
--- a/warp/backend.go
+++ b/warp/backend.go
@@ -11,20 +11,19 @@ import (
 	"github.com/ava-labs/avalanchego/cache"
 	"github.com/ava-labs/avalanchego/database"
 	"github.com/ava-labs/avalanchego/ids"
+	"github.com/ava-labs/avalanchego/network/p2p/acp118"
 	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
-	"github.com/ava-labs/avalanchego/utils/crypto/bls"
 	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
 	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
-	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 )
 
 var (
 	_                         Backend = &backend{}
 	errParsingOffChainMessage         = errors.New("failed to parse off-chain message")
-)
 
-const batchSize = ethdb.IdealBatchSize
+	messageCacheSize = 500
+)
 
 type BlockClient interface {
 	GetAcceptedBlock(ctx context.Context, blockID ids.ID) (snowman.Block, error)
@@ -36,19 +35,18 @@ type Backend interface {
 	// AddMessage signs [unsignedMessage] and adds it to the warp backend database
 	AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error
 
-	// GetMessageSignature returns the signature of the requested message hash.
-	GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error)
+	// GetMessageSignature validates the message and returns the signature of the requested message.
+	GetMessageSignature(ctx context.Context, message *avalancheWarp.UnsignedMessage) ([]byte, error)
 
-	// GetBlockSignature returns the signature of the requested message hash.
-	GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error)
+	// GetBlockSignature returns the signature of a hash payload containing blockID if it's the ID of an accepted block.
+	GetBlockSignature(ctx context.Context, blockID ids.ID) ([]byte, error)
 
 	// GetMessage retrieves the [unsignedMessage] from the warp backend database if available
-	// TODO: After E-Upgrade, the backend no longer needs to store the mapping from messageHash
+	// TODO: After Etna, the backend no longer needs to store the mapping from messageHash
 	// to unsignedMessage (and this method can be removed).
 	GetMessage(messageHash ids.ID) (*avalancheWarp.UnsignedMessage, error)
 
-	// Clear clears the entire db
-	Clear() error
+	acp118.Verifier
 }
 
 // backend implements Backend, keeps track of warp messages, and generates message signatures.
@@ -58,10 +56,10 @@ type backend struct {
 	db                        database.Database
 	warpSigner                avalancheWarp.Signer
 	blockClient               BlockClient
-	messageSignatureCache     *cache.LRU[ids.ID, [bls.SignatureLen]byte]
-	blockSignatureCache       *cache.LRU[ids.ID, [bls.SignatureLen]byte]
+	signatureCache            cache.Cacher[ids.ID, []byte]
 	messageCache              *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]
 	offchainAddressedCallMsgs map[ids.ID]*avalancheWarp.UnsignedMessage
+	stats                     *verifierStats
 }
 
 // NewBackend creates a new Backend, and initializes the signature cache and message tracking database.
@@ -71,7 +69,7 @@ func NewBackend(
 	warpSigner avalancheWarp.Signer,
 	blockClient BlockClient,
 	db database.Database,
-	cacheSize int,
+	signatureCache cache.Cacher[ids.ID, []byte],
 	offchainMessages [][]byte,
 ) (Backend, error) {
 	b := &backend{
@@ -80,9 +78,9 @@ func NewBackend(
 		db:                        db,
 		warpSigner:                warpSigner,
 		blockClient:               blockClient,
-		messageSignatureCache:     &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: cacheSize},
-		blockSignatureCache:       &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: cacheSize},
-		messageCache:              &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: cacheSize},
+		signatureCache:            signatureCache,
+		messageCache:              &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: messageCacheSize},
+		stats:                     newVerifierStats(),
 		offchainAddressedCallMsgs: make(map[ids.ID]*avalancheWarp.UnsignedMessage),
 	}
 	return b, b.initOffChainMessages(offchainMessages)
@@ -113,15 +111,9 @@ func (b *backend) initOffChainMessages(offchainMessages [][]byte) error {
 	return nil
 }
 
-func (b *backend) Clear() error {
-	b.messageSignatureCache.Flush()
-	b.blockSignatureCache.Flush()
-	b.messageCache.Flush()
-	return database.Clear(b.db, batchSize)
-}
-
 func (b *backend) AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error {
 	messageID := unsignedMessage.ID()
+	log.Debug("Adding warp message to backend", "messageID", messageID)
 
 	// In the case when a node restarts, and possibly changes its bls key, the cache gets emptied but the database does not.
 	// So to avoid having incorrect signatures saved in the database after a bls key change, we save the full message in the database.
@@ -130,68 +122,52 @@ func (b *backend) AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) err
 		return fmt.Errorf("failed to put warp signature in db: %w", err)
 	}
 
-	var signature [bls.SignatureLen]byte
-	sig, err := b.warpSigner.Sign(unsignedMessage)
-	if err != nil {
+	if _, err := b.signMessage(unsignedMessage); err != nil {
 		return fmt.Errorf("failed to sign warp message: %w", err)
 	}
-
-	copy(signature[:], sig)
-	b.messageSignatureCache.Put(messageID, signature)
-	log.Debug("Adding warp message to backend", "messageID", messageID)
 	return nil
 }
 
-func (b *backend) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) {
+func (b *backend) GetMessageSignature(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage) ([]byte, error) {
+	messageID := unsignedMessage.ID()
+
 	log.Debug("Getting warp message from backend", "messageID", messageID)
-	if sig, ok := b.messageSignatureCache.Get(messageID); ok {
+	if sig, ok := b.signatureCache.Get(messageID); ok {
 		return sig, nil
 	}
 
-	unsignedMessage, err := b.GetMessage(messageID)
-	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err)
+	if err := b.Verify(ctx, unsignedMessage, nil); err != nil {
+		return nil, fmt.Errorf("failed to validate warp message: %w", err)
 	}
-
-	var signature [bls.SignatureLen]byte
-	sig, err := b.warpSigner.Sign(unsignedMessage)
-	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to sign warp message: %w", err)
-	}
-
-	copy(signature[:], sig)
-	b.messageSignatureCache.Put(messageID, signature)
-	return signature, nil
+	return b.signMessage(unsignedMessage)
 }
 
-func (b *backend) GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) {
+func (b *backend) GetBlockSignature(ctx context.Context, blockID ids.ID) ([]byte, error) {
 	log.Debug("Getting block from backend", "blockID", blockID)
-	if sig, ok := b.blockSignatureCache.Get(blockID); ok {
-		return sig, nil
-	}
 
-	_, err := b.blockClient.GetAcceptedBlock(context.TODO(), blockID)
-	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to get block %s: %w", blockID, err)
-	}
-
-	var signature [bls.SignatureLen]byte
 	blockHashPayload, err := payload.NewHash(blockID)
 	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to create new block hash payload: %w", err)
+		return nil, fmt.Errorf("failed to create new block hash payload: %w", err)
 	}
+
 	unsignedMessage, err := avalancheWarp.NewUnsignedMessage(b.networkID, b.sourceChainID, blockHashPayload.Bytes())
 	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to create new unsigned warp message: %w", err)
+		return nil, fmt.Errorf("failed to create new unsigned warp message: %w", err)
 	}
-	sig, err := b.warpSigner.Sign(unsignedMessage)
-	if err != nil {
-		return [bls.SignatureLen]byte{}, fmt.Errorf("failed to sign warp message: %w", err)
+
+	if sig, ok := b.signatureCache.Get(unsignedMessage.ID()); ok {
+		return sig, nil
+	}
+
+	if err := b.verifyBlockMessage(ctx, blockHashPayload); err != nil {
+		return nil, fmt.Errorf("failed to validate block message: %w", err)
 	}
 
-	copy(signature[:], sig)
-	b.blockSignatureCache.Put(blockID, signature)
-	return signature, nil
+	sig, err := b.signMessage(unsignedMessage)
+	if err != nil {
+		return nil, fmt.Errorf("failed to sign block message: %w", err)
+	}
+	return sig, nil
 }
 
 func (b *backend) GetMessage(messageID ids.ID) (*avalancheWarp.UnsignedMessage, error) {
@@ -215,3 +191,13 @@ func (b *backend) GetMessage(messageID ids.ID) (*avalancheWarp.UnsignedMessage,
 
 	return unsignedMessage, nil
 }
+
+func (b *backend) signMessage(unsignedMessage *avalancheWarp.UnsignedMessage) ([]byte, error) {
+	sig, err := b.warpSigner.Sign(unsignedMessage)
+	if err != nil {
+		return nil, fmt.Errorf("failed to sign warp message: %w", err)
+	}
+
+	b.signatureCache.Put(unsignedMessage.ID(), sig)
+	return sig, nil
+}
diff --git a/warp/backend_test.go b/warp/backend_test.go
index 7b2208a2d4..5a0a64a0c2 100644
--- a/warp/backend_test.go
+++ b/warp/backend_test.go
@@ -4,13 +4,14 @@
 package warp
 
 import (
+	"context"
 	"testing"
 
+	"github.com/ava-labs/avalanchego/cache"
 	"github.com/ava-labs/avalanchego/database/memdb"
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/utils"
 	"github.com/ava-labs/avalanchego/utils/crypto/bls"
-	"github.com/ava-labs/avalanchego/utils/hashing"
 	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
 	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
 	"github.com/ava-labs/coreth/warp/warptest"
@@ -36,57 +37,14 @@ func init() {
 	}
 }
 
-func TestClearDB(t *testing.T) {
-	db := memdb.New()
-
-	sk, err := bls.NewSecretKey()
-	require.NoError(t, err)
-	warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
-	backendIntf, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500, nil)
-	require.NoError(t, err)
-	backend, ok := backendIntf.(*backend)
-	require.True(t, ok)
-
-	// use multiple messages to test that all messages get cleared
-	payloads := [][]byte{[]byte("test1"), []byte("test2"), []byte("test3"), []byte("test4"), []byte("test5")}
-	messageIDs := []ids.ID{}
-
-	// add all messages
-	for _, payload := range payloads {
-		unsignedMsg, err := avalancheWarp.NewUnsignedMessage(networkID, sourceChainID, payload)
-		require.NoError(t, err)
-		messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes())
-		messageIDs = append(messageIDs, messageID)
-		err = backend.AddMessage(unsignedMsg)
-		require.NoError(t, err)
-		// ensure that the message was added
-		_, err = backend.GetMessageSignature(messageID)
-		require.NoError(t, err)
-	}
-
-	err = backend.Clear()
-	require.NoError(t, err)
-	require.Zero(t, backend.messageCache.Len())
-	require.Zero(t, backend.messageSignatureCache.Len())
-	require.Zero(t, backend.blockSignatureCache.Len())
-	it := db.NewIterator()
-	defer it.Release()
-	require.False(t, it.Next())
-
-	// ensure all messages have been deleted
-	for _, messageID := range messageIDs {
-		_, err := backend.GetMessageSignature(messageID)
-		require.ErrorContains(t, err, "failed to get warp message")
-	}
-}
-
 func TestAddAndGetValidMessage(t *testing.T) {
 	db := memdb.New()
 
 	sk, err := bls.NewSecretKey()
 	require.NoError(t, err)
 	warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
-	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500, nil)
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
+	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
 	require.NoError(t, err)
 
 	// Add testUnsignedMessage to the warp backend
@@ -94,8 +52,7 @@ func TestAddAndGetValidMessage(t *testing.T) {
 	require.NoError(t, err)
 
 	// Verify that a signature is returned successfully, and compare to expected signature.
-	messageID := testUnsignedMessage.ID()
-	signature, err := backend.GetMessageSignature(messageID)
+	signature, err := backend.GetMessageSignature(context.TODO(), testUnsignedMessage)
 	require.NoError(t, err)
 
 	expectedSig, err := warpSigner.Sign(testUnsignedMessage)
@@ -109,12 +66,12 @@ func TestAddAndGetUnknownMessage(t *testing.T) {
 	sk, err := bls.NewSecretKey()
 	require.NoError(t, err)
 	warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
-	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 500, nil)
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
+	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
 	require.NoError(t, err)
 
 	// Try getting a signature for a message that was not added.
-	messageID := testUnsignedMessage.ID()
-	_, err = backend.GetMessageSignature(messageID)
+	_, err = backend.GetMessageSignature(context.TODO(), testUnsignedMessage)
 	require.Error(t, err)
 }
 
@@ -128,7 +85,8 @@ func TestGetBlockSignature(t *testing.T) {
 	sk, err := bls.NewSecretKey()
 	require.NoError(err)
 	warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
-	backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, db, 500, nil)
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500}
+	backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, db, messageSignatureCache, nil)
 	require.NoError(err)
 
 	blockHashPayload, err := payload.NewHash(blkID)
@@ -138,11 +96,11 @@ func TestGetBlockSignature(t *testing.T) {
 	expectedSig, err := warpSigner.Sign(unsignedMessage)
 	require.NoError(err)
 
-	signature, err := backend.GetBlockSignature(blkID)
+	signature, err := backend.GetBlockSignature(context.TODO(), blkID)
 	require.NoError(err)
 	require.Equal(expectedSig, signature[:])
 
-	_, err = backend.GetBlockSignature(ids.GenerateTestID())
+	_, err = backend.GetBlockSignature(context.TODO(), ids.GenerateTestID())
 	require.Error(err)
 }
 
@@ -154,7 +112,8 @@ func TestZeroSizedCache(t *testing.T) {
 	warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID)
 
 	// Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0.
-	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 0, nil)
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0}
+	backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil)
 	require.NoError(t, err)
 
 	// Add testUnsignedMessage to the warp backend
@@ -162,8 +121,7 @@ func TestZeroSizedCache(t *testing.T) {
 	require.NoError(t, err)
 
 	// Verify that a signature is returned successfully, and compare to expected signature.
-	messageID := testUnsignedMessage.ID()
-	signature, err := backend.GetMessageSignature(messageID)
+	signature, err := backend.GetMessageSignature(context.TODO(), testUnsignedMessage)
 	require.NoError(t, err)
 
 	expectedSig, err := warpSigner.Sign(testUnsignedMessage)
@@ -192,7 +150,7 @@ func TestOffChainMessages(t *testing.T) {
 				require.NoError(err)
 				require.Equal(testUnsignedMessage.Bytes(), msg.Bytes())
 
-				signature, err := b.GetMessageSignature(testUnsignedMessage.ID())
+				signature, err := b.GetMessageSignature(context.TODO(), testUnsignedMessage)
 				require.NoError(err)
 				expectedSignatureBytes, err := warpSigner.Sign(msg)
 				require.NoError(err)
@@ -208,7 +166,8 @@ func TestOffChainMessages(t *testing.T) {
 			require := require.New(t)
 			db := memdb.New()
 
-			backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, 0, test.offchainMessages)
+			messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0}
+			backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, test.offchainMessages)
 			require.ErrorIs(err, test.err)
 			if test.check != nil {
 				test.check(require, backend)
diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go
index 8a8b4e4e1e..bb9c2c7855 100644
--- a/warp/handlers/signature_request.go
+++ b/warp/handlers/signature_request.go
@@ -16,7 +16,7 @@ import (
 )
 
 // SignatureRequestHandler serves warp signature requests. It is a peer.RequestHandler for message.MessageSignatureRequest.
-// TODO: After E-Upgrade, this handler can be removed and SignatureRequestHandlerP2P is sufficient.
+// TODO: After Etna, this handler can be removed and SignatureRequestHandlerP2P is sufficient.
 type SignatureRequestHandler struct {
 	backend warp.Backend
 	codec   codec.Manager
@@ -45,13 +45,20 @@ func (s *SignatureRequestHandler) OnMessageSignatureRequest(ctx context.Context,
 		s.stats.UpdateMessageSignatureRequestTime(time.Since(startTime))
 	}()
 
-	signature, err := s.backend.GetMessageSignature(signatureRequest.MessageID)
+	var signature [bls.SignatureLen]byte
+	unsignedMessage, err := s.backend.GetMessage(signatureRequest.MessageID)
 	if err != nil {
-		log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID)
+		log.Debug("Unknown warp message requested", "messageID", signatureRequest.MessageID)
 		s.stats.IncMessageSignatureMiss()
-		signature = [bls.SignatureLen]byte{}
 	} else {
-		s.stats.IncMessageSignatureHit()
+		sig, err := s.backend.GetMessageSignature(ctx, unsignedMessage)
+		if err != nil {
+			log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID)
+			s.stats.IncMessageSignatureMiss()
+		} else {
+			s.stats.IncMessageSignatureHit()
+			copy(signature[:], sig)
+		}
 	}
 
 	response := message.SignatureResponse{Signature: signature}
@@ -73,13 +80,14 @@ func (s *SignatureRequestHandler) OnBlockSignatureRequest(ctx context.Context, n
 		s.stats.UpdateBlockSignatureRequestTime(time.Since(startTime))
 	}()
 
-	signature, err := s.backend.GetBlockSignature(request.BlockID)
+	var signature [bls.SignatureLen]byte
+	sig, err := s.backend.GetBlockSignature(ctx, request.BlockID)
 	if err != nil {
 		log.Debug("Unknown warp signature requested", "blockID", request.BlockID)
 		s.stats.IncBlockSignatureMiss()
-		signature = [bls.SignatureLen]byte{}
 	} else {
 		s.stats.IncBlockSignatureHit()
+		copy(signature[:], sig)
 	}
 
 	response := message.SignatureResponse{Signature: signature}
diff --git a/warp/handlers/signature_request_p2p.go b/warp/handlers/signature_request_p2p.go
deleted file mode 100644
index 47fe2d0908..0000000000
--- a/warp/handlers/signature_request_p2p.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// (c) 2024, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package handlers
-
-import (
-	"context"
-	"fmt"
-	"time"
-
-	"github.com/ava-labs/avalanchego/codec"
-	"github.com/ava-labs/avalanchego/ids"
-	"github.com/ava-labs/avalanchego/network/p2p"
-	"github.com/ava-labs/avalanchego/proto/pb/sdk"
-	"github.com/ava-labs/avalanchego/snow/engine/common"
-	"github.com/ava-labs/avalanchego/utils/crypto/bls"
-	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
-	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
-	"github.com/ava-labs/coreth/warp"
-	"google.golang.org/protobuf/proto"
-)
-
-var _ p2p.Handler = (*SignatureRequestHandlerP2P)(nil)
-
-const (
-	ErrFailedToParse = iota
-	ErrFailedToGetSig
-	ErrFailedToMarshal
-)
-
-// SignatureRequestHandlerP2P serves warp signature requests using the p2p
-// framework from avalanchego. It is a peer.RequestHandler for
-// message.MessageSignatureRequest.
-type SignatureRequestHandlerP2P struct {
-	backend warp.Backend
-	codec   codec.Manager
-	stats   *handlerStats
-}
-
-func NewSignatureRequestHandlerP2P(backend warp.Backend, codec codec.Manager) *SignatureRequestHandlerP2P {
-	return &SignatureRequestHandlerP2P{
-		backend: backend,
-		codec:   codec,
-		stats:   newStats(),
-	}
-}
-
-func (s *SignatureRequestHandlerP2P) AppRequest(
-	ctx context.Context,
-	nodeID ids.NodeID,
-	deadline time.Time,
-	requestBytes []byte,
-) ([]byte, *common.AppError) {
-	// Per ACP-118, the requestBytes are the serialized form of
-	// sdk.SignatureRequest.
-	req := new(sdk.SignatureRequest)
-	if err := proto.Unmarshal(requestBytes, req); err != nil {
-		return nil, &common.AppError{
-			Code:    ErrFailedToParse,
-			Message: "failed to unmarshal request: " + err.Error(),
-		}
-	}
-
-	unsignedMessage, err := avalancheWarp.ParseUnsignedMessage(req.Message)
-	if err != nil {
-		return nil, &common.AppError{
-			Code:    ErrFailedToParse,
-			Message: "failed to parse unsigned message: " + err.Error(),
-		}
-	}
-	parsed, err := payload.Parse(unsignedMessage.Payload)
-	if err != nil {
-		return nil, &common.AppError{
-			Code:    ErrFailedToParse,
-			Message: "failed to parse payload: " + err.Error(),
-		}
-	}
-
-	var sig [bls.SignatureLen]byte
-	switch p := parsed.(type) {
-	case *payload.AddressedCall:
-		// Note we pass the unsigned message ID to GetMessageSignature since
-		// that is what the backend expects.
-		// However, we verify the types and format of the payload to ensure
-		// the message conforms to the ACP-118 spec.
-		sig, err = s.GetMessageSignature(unsignedMessage.ID())
-		if err != nil {
-			s.stats.IncMessageSignatureMiss()
-		} else {
-			s.stats.IncMessageSignatureHit()
-		}
-	case *payload.Hash:
-		sig, err = s.GetBlockSignature(p.Hash)
-		if err != nil {
-			s.stats.IncBlockSignatureMiss()
-		} else {
-			s.stats.IncBlockSignatureHit()
-		}
-	default:
-		return nil, &common.AppError{
-			Code:    ErrFailedToParse,
-			Message: fmt.Sprintf("unknown payload type: %T", p),
-		}
-	}
-	if err != nil {
-		return nil, &common.AppError{
-			Code:    ErrFailedToGetSig,
-			Message: "failed to get signature: " + err.Error(),
-		}
-	}
-
-	// Per ACP-118, the responseBytes are the serialized form of
-	// sdk.SignatureResponse.
-	resp := &sdk.SignatureResponse{Signature: sig[:]}
-	respBytes, err := proto.Marshal(resp)
-	if err != nil {
-		return nil, &common.AppError{
-			Code:    ErrFailedToMarshal,
-			Message: "failed to marshal response: " + err.Error(),
-		}
-	}
-	return respBytes, nil
-}
-
-func (s *SignatureRequestHandlerP2P) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) {
-	startTime := time.Now()
-	s.stats.IncMessageSignatureRequest()
-
-	// Always report signature request time
-	defer func() {
-		s.stats.UpdateMessageSignatureRequestTime(time.Since(startTime))
-	}()
-
-	return s.backend.GetMessageSignature(messageID)
-}
-
-func (s *SignatureRequestHandlerP2P) GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) {
-	startTime := time.Now()
-	s.stats.IncBlockSignatureRequest()
-
-	// Always report signature request time
-	defer func() {
-		s.stats.UpdateBlockSignatureRequestTime(time.Since(startTime))
-	}()
-
-	return s.backend.GetBlockSignature(blockID)
-}
-
-func (s *SignatureRequestHandlerP2P) AppGossip(
-	ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) {
-}
diff --git a/warp/handlers/signature_request_p2p_test.go b/warp/handlers/signature_request_p2p_test.go
deleted file mode 100644
index 36d6507771..0000000000
--- a/warp/handlers/signature_request_p2p_test.go
+++ /dev/null
@@ -1,232 +0,0 @@
-// (c) 2024, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package handlers
-
-import (
-	"context"
-	"testing"
-	"time"
-
-	"github.com/ava-labs/avalanchego/database/memdb"
-	"github.com/ava-labs/avalanchego/ids"
-	"github.com/ava-labs/avalanchego/proto/pb/sdk"
-	"github.com/ava-labs/avalanchego/snow/engine/common"
-	"github.com/ava-labs/avalanchego/utils/crypto/bls"
-	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
-	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
-	"github.com/ava-labs/coreth/plugin/evm/message"
-	"github.com/ava-labs/coreth/utils"
-	"github.com/ava-labs/coreth/warp"
-	"github.com/ava-labs/coreth/warp/warptest"
-	"github.com/stretchr/testify/require"
-	"google.golang.org/protobuf/proto"
-)
-
-func TestMessageSignatureHandlerP2P(t *testing.T) {
-	database := memdb.New()
-	snowCtx := utils.TestSnowContext()
-	blsSecretKey, err := bls.NewSecretKey()
-	require.NoError(t, err)
-	warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID)
-
-	addressedPayload, err := payload.NewAddressedCall([]byte{1, 2, 3}, []byte{1, 2, 3})
-	require.NoError(t, err)
-	offchainMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, addressedPayload.Bytes())
-	require.NoError(t, err)
-
-	backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, 100, [][]byte{offchainMessage.Bytes()})
-	require.NoError(t, err)
-
-	offchainPayload, err := payload.NewAddressedCall([]byte{0, 0, 0}, []byte("test"))
-	require.NoError(t, err)
-	msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, offchainPayload.Bytes())
-	require.NoError(t, err)
-	messageID := msg.ID()
-	require.NoError(t, backend.AddMessage(msg))
-	signature, err := backend.GetMessageSignature(messageID)
-	require.NoError(t, err)
-	offchainSignature, err := backend.GetMessageSignature(offchainMessage.ID())
-	require.NoError(t, err)
-
-	unknownPayload, err := payload.NewAddressedCall([]byte{0, 0, 0}, []byte("unknown message"))
-	require.NoError(t, err)
-	unknownMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, unknownPayload.Bytes())
-	require.NoError(t, err)
-
-	tests := map[string]struct {
-		setup       func() (request sdk.SignatureRequest, expectedResponse []byte)
-		verifyStats func(t *testing.T, stats *handlerStats)
-		err         error
-	}{
-		"known message": {
-			setup: func() (request sdk.SignatureRequest, expectedResponse []byte) {
-				return sdk.SignatureRequest{Message: msg.Bytes()}, signature[:]
-			},
-			verifyStats: func(t *testing.T, stats *handlerStats) {
-				require.EqualValues(t, 1, stats.messageSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 1, stats.messageSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureMiss.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureMiss.Snapshot().Count())
-			},
-		},
-		"offchain message": {
-			setup: func() (request sdk.SignatureRequest, expectedResponse []byte) {
-				return sdk.SignatureRequest{Message: offchainMessage.Bytes()}, offchainSignature[:]
-			},
-			verifyStats: func(t *testing.T, stats *handlerStats) {
-				require.EqualValues(t, 1, stats.messageSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 1, stats.messageSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureMiss.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureMiss.Snapshot().Count())
-			},
-		},
-		"unknown message": {
-			setup: func() (request sdk.SignatureRequest, expectedResponse []byte) {
-				return sdk.SignatureRequest{Message: unknownMessage.Bytes()}, nil
-			},
-			verifyStats: func(t *testing.T, stats *handlerStats) {
-				require.EqualValues(t, 1, stats.messageSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 1, stats.messageSignatureMiss.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureMiss.Snapshot().Count())
-			},
-			err: &common.AppError{Code: ErrFailedToGetSig},
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			handler := NewSignatureRequestHandlerP2P(backend, message.Codec)
-			handler.stats.Clear()
-
-			request, expectedResponse := test.setup()
-			requestBytes, err := proto.Marshal(&request)
-			require.NoError(t, err)
-			responseBytes, appErr := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, requestBytes)
-			if test.err != nil {
-				require.ErrorIs(t, appErr, test.err)
-			} else {
-				require.Nil(t, appErr)
-			}
-
-			test.verifyStats(t, handler.stats)
-
-			// If the expected response is empty, assert that the handler returns an empty response and return early.
-			if len(expectedResponse) == 0 {
-				require.Len(t, responseBytes, 0, "expected response to be empty")
-				return
-			}
-			var response sdk.SignatureResponse
-			err = proto.Unmarshal(responseBytes, &response)
-			require.NoError(t, err, "error unmarshalling SignatureResponse")
-
-			require.Equal(t, expectedResponse, response.Signature)
-		})
-	}
-}
-
-func TestBlockSignatureHandlerP2P(t *testing.T) {
-	database := memdb.New()
-	snowCtx := utils.TestSnowContext()
-	blsSecretKey, err := bls.NewSecretKey()
-	require.NoError(t, err)
-
-	warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID)
-	blkID := ids.GenerateTestID()
-	blockClient := warptest.MakeBlockClient(blkID)
-	backend, err := warp.NewBackend(
-		snowCtx.NetworkID,
-		snowCtx.ChainID,
-		warpSigner,
-		blockClient,
-		database,
-		100,
-		nil,
-	)
-	require.NoError(t, err)
-
-	signature, err := backend.GetBlockSignature(blkID)
-	require.NoError(t, err)
-	unknownBlockID := ids.GenerateTestID()
-
-	toMessageBytes := func(id ids.ID) []byte {
-		idPayload, err := payload.NewHash(id)
-		require.NoError(t, err)
-
-		msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, idPayload.Bytes())
-		require.NoError(t, err)
-
-		return msg.Bytes()
-	}
-
-	tests := map[string]struct {
-		setup       func() (request sdk.SignatureRequest, expectedResponse []byte)
-		verifyStats func(t *testing.T, stats *handlerStats)
-		err         error
-	}{
-		"known block": {
-			setup: func() (request sdk.SignatureRequest, expectedResponse []byte) {
-				return sdk.SignatureRequest{Message: toMessageBytes(blkID)}, signature[:]
-			},
-			verifyStats: func(t *testing.T, stats *handlerStats) {
-				require.EqualValues(t, 0, stats.messageSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureMiss.Snapshot().Count())
-				require.EqualValues(t, 1, stats.blockSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 1, stats.blockSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureMiss.Snapshot().Count())
-			},
-		},
-		"unknown block": {
-			setup: func() (request sdk.SignatureRequest, expectedResponse []byte) {
-				return sdk.SignatureRequest{Message: toMessageBytes(unknownBlockID)}, nil
-			},
-			verifyStats: func(t *testing.T, stats *handlerStats) {
-				require.EqualValues(t, 0, stats.messageSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 0, stats.messageSignatureMiss.Snapshot().Count())
-				require.EqualValues(t, 1, stats.blockSignatureRequest.Snapshot().Count())
-				require.EqualValues(t, 0, stats.blockSignatureHit.Snapshot().Count())
-				require.EqualValues(t, 1, stats.blockSignatureMiss.Snapshot().Count())
-			},
-			err: &common.AppError{Code: ErrFailedToGetSig},
-		},
-	}
-
-	for name, test := range tests {
-		t.Run(name, func(t *testing.T) {
-			handler := NewSignatureRequestHandlerP2P(backend, message.Codec)
-			handler.stats.Clear()
-
-			request, expectedResponse := test.setup()
-			requestBytes, err := proto.Marshal(&request)
-			require.NoError(t, err)
-			responseBytes, appErr := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, requestBytes)
-			if test.err != nil {
-				require.ErrorIs(t, appErr, test.err)
-			} else {
-				require.Nil(t, appErr)
-			}
-
-			test.verifyStats(t, handler.stats)
-
-			// If the expected response is empty, assert that the handler returns an empty response and return early.
-			if len(expectedResponse) == 0 {
-				require.Len(t, responseBytes, 0, "expected response to be empty")
-				return
-			}
-			var response sdk.SignatureResponse
-			err = proto.Unmarshal(responseBytes, &response)
-			require.NoError(t, err, "error unmarshalling SignatureResponse")
-
-			require.Equal(t, expectedResponse, response.Signature)
-		})
-	}
-}
diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go
index b50a1b519b..dd2271988b 100644
--- a/warp/handlers/signature_request_test.go
+++ b/warp/handlers/signature_request_test.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"testing"
 
+	"github.com/ava-labs/avalanchego/cache"
 	"github.com/ava-labs/avalanchego/database/memdb"
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/utils/crypto/bls"
@@ -31,16 +32,17 @@ func TestMessageSignatureHandler(t *testing.T) {
 	offchainMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, addressedPayload.Bytes())
 	require.NoError(t, err)
 
-	backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, 100, [][]byte{offchainMessage.Bytes()})
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 100}
+	backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, messageSignatureCache, [][]byte{offchainMessage.Bytes()})
 	require.NoError(t, err)
 
 	msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, []byte("test"))
 	require.NoError(t, err)
 	messageID := msg.ID()
 	require.NoError(t, backend.AddMessage(msg))
-	signature, err := backend.GetMessageSignature(messageID)
+	signature, err := backend.GetMessageSignature(context.TODO(), msg)
 	require.NoError(t, err)
-	offchainSignature, err := backend.GetMessageSignature(offchainMessage.ID())
+	offchainSignature, err := backend.GetMessageSignature(context.TODO(), offchainMessage)
 	require.NoError(t, err)
 
 	unknownMessageID := ids.GenerateTestID()
@@ -101,7 +103,6 @@ func TestMessageSignatureHandler(t *testing.T) {
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
 			handler := NewSignatureRequestHandler(backend, message.Codec)
-			handler.stats.Clear()
 
 			request, expectedResponse := test.setup()
 			responseBytes, err := handler.OnMessageSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request)
@@ -132,18 +133,19 @@ func TestBlockSignatureHandler(t *testing.T) {
 	warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID)
 	blkID := ids.GenerateTestID()
 	blockClient := warptest.MakeBlockClient(blkID)
+	messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 100}
 	backend, err := warp.NewBackend(
 		snowCtx.NetworkID,
 		snowCtx.ChainID,
 		warpSigner,
 		blockClient,
 		database,
-		100,
+		messageSignatureCache,
 		nil,
 	)
 	require.NoError(t, err)
 
-	signature, err := backend.GetBlockSignature(blkID)
+	signature, err := backend.GetBlockSignature(context.TODO(), blkID)
 	require.NoError(t, err)
 	unknownMessageID := ids.GenerateTestID()
 
@@ -188,7 +190,6 @@ func TestBlockSignatureHandler(t *testing.T) {
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
 			handler := NewSignatureRequestHandler(backend, message.Codec)
-			handler.stats.Clear()
 
 			request, expectedResponse := test.setup()
 			responseBytes, err := handler.OnBlockSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request)
diff --git a/warp/handlers/stats.go b/warp/handlers/stats.go
index 9e2ea373aa..ef3f31ae9a 100644
--- a/warp/handlers/stats.go
+++ b/warp/handlers/stats.go
@@ -24,14 +24,14 @@ type handlerStats struct {
 
 func newStats() *handlerStats {
 	return &handlerStats{
-		messageSignatureRequest:         metrics.GetOrRegisterCounter("message_signature_request_count", nil),
-		messageSignatureHit:             metrics.GetOrRegisterCounter("message_signature_request_hit", nil),
-		messageSignatureMiss:            metrics.GetOrRegisterCounter("message_signature_request_miss", nil),
-		messageSignatureRequestDuration: metrics.GetOrRegisterGauge("message_signature_request_duration", nil),
-		blockSignatureRequest:           metrics.GetOrRegisterCounter("block_signature_request_count", nil),
-		blockSignatureHit:               metrics.GetOrRegisterCounter("block_signature_request_hit", nil),
-		blockSignatureMiss:              metrics.GetOrRegisterCounter("block_signature_request_miss", nil),
-		blockSignatureRequestDuration:   metrics.GetOrRegisterGauge("block_signature_request_duration", nil),
+		messageSignatureRequest:         metrics.NewRegisteredCounter("message_signature_request_count", nil),
+		messageSignatureHit:             metrics.NewRegisteredCounter("message_signature_request_hit", nil),
+		messageSignatureMiss:            metrics.NewRegisteredCounter("message_signature_request_miss", nil),
+		messageSignatureRequestDuration: metrics.NewRegisteredGauge("message_signature_request_duration", nil),
+		blockSignatureRequest:           metrics.NewRegisteredCounter("block_signature_request_count", nil),
+		blockSignatureHit:               metrics.NewRegisteredCounter("block_signature_request_hit", nil),
+		blockSignatureMiss:              metrics.NewRegisteredCounter("block_signature_request_miss", nil),
+		blockSignatureRequestDuration:   metrics.NewRegisteredGauge("block_signature_request_duration", nil),
 	}
 }
 
@@ -47,13 +47,3 @@ func (h *handlerStats) IncBlockSignatureMiss()    { h.blockSignatureMiss.Inc(1)
 func (h *handlerStats) UpdateBlockSignatureRequestTime(duration time.Duration) {
 	h.blockSignatureRequestDuration.Inc(int64(duration))
 }
-func (h *handlerStats) Clear() {
-	h.messageSignatureRequest.Clear()
-	h.messageSignatureHit.Clear()
-	h.messageSignatureMiss.Clear()
-	h.messageSignatureRequestDuration.Update(0)
-	h.blockSignatureRequest.Clear()
-	h.blockSignatureHit.Clear()
-	h.blockSignatureMiss.Clear()
-	h.blockSignatureRequestDuration.Update(0)
-}
diff --git a/warp/service.go b/warp/service.go
index 8b92da80db..610fc85a91 100644
--- a/warp/service.go
+++ b/warp/service.go
@@ -54,7 +54,11 @@ func (a *API) GetMessage(ctx context.Context, messageID ids.ID) (hexutil.Bytes,
 
 // GetMessageSignature returns the BLS signature associated with a messageID.
 func (a *API) GetMessageSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) {
-	signature, err := a.backend.GetMessageSignature(messageID)
+	unsignedMessage, err := a.backend.GetMessage(messageID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get message %s with error %w", messageID, err)
+	}
+	signature, err := a.backend.GetMessageSignature(ctx, unsignedMessage)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get signature for message %s with error %w", messageID, err)
 	}
@@ -63,7 +67,7 @@ func (a *API) GetMessageSignature(ctx context.Context, messageID ids.ID) (hexuti
 
 // GetBlockSignature returns the BLS signature associated with a blockID.
 func (a *API) GetBlockSignature(ctx context.Context, blockID ids.ID) (hexutil.Bytes, error) {
-	signature, err := a.backend.GetBlockSignature(blockID)
+	signature, err := a.backend.GetBlockSignature(ctx, blockID)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get signature for block %s with error %w", blockID, err)
 	}
diff --git a/warp/verifier_backend.go b/warp/verifier_backend.go
new file mode 100644
index 0000000000..c70563c585
--- /dev/null
+++ b/warp/verifier_backend.go
@@ -0,0 +1,64 @@
+// (c) 2024, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package warp
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/ava-labs/avalanchego/snow/engine/common"
+	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
+)
+
+const (
+	ParseErrCode = iota + 1
+	VerifyErrCode
+)
+
+// Verify verifies the signature of the message
+// It also implements the acp118.Verifier interface
+func (b *backend) Verify(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage, _ []byte) *common.AppError {
+	messageID := unsignedMessage.ID()
+	// Known on-chain messages should be signed
+	if _, err := b.GetMessage(messageID); err == nil {
+		return nil
+	}
+
+	parsed, err := payload.Parse(unsignedMessage.Payload)
+	if err != nil {
+		b.stats.IncMessageParseFail()
+		return &common.AppError{
+			Code:    ParseErrCode,
+			Message: "failed to parse payload: " + err.Error(),
+		}
+	}
+
+	switch p := parsed.(type) {
+	case *payload.Hash:
+		return b.verifyBlockMessage(ctx, p)
+	default:
+		b.stats.IncMessageParseFail()
+		return &common.AppError{
+			Code:    ParseErrCode,
+			Message: fmt.Sprintf("unknown payload type: %T", p),
+		}
+	}
+}
+
+// verifyBlockMessage returns nil if blockHashPayload contains the ID
+// of an accepted block indicating it should be signed by the VM.
+func (b *backend) verifyBlockMessage(ctx context.Context, blockHashPayload *payload.Hash) *common.AppError {
+	blockID := blockHashPayload.Hash
+	_, err := b.blockClient.GetAcceptedBlock(ctx, blockID)
+	if err != nil {
+		b.stats.IncBlockSignatureValidationFail()
+		return &common.AppError{
+			Code:    VerifyErrCode,
+			Message: fmt.Sprintf("failed to get block %s: %s", blockID, err.Error()),
+		}
+	}
+
+	return nil
+}
diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go
new file mode 100644
index 0000000000..5ea3a37e0f
--- /dev/null
+++ b/warp/verifier_backend_test.go
@@ -0,0 +1,255 @@
+// (c) 2024, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package warp
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/ava-labs/avalanchego/cache"
+	"github.com/ava-labs/avalanchego/database/memdb"
+	"github.com/ava-labs/avalanchego/ids"
+	"github.com/ava-labs/avalanchego/network/p2p/acp118"
+	"github.com/ava-labs/avalanchego/proto/pb/sdk"
+	"github.com/ava-labs/avalanchego/snow/engine/common"
+	"github.com/ava-labs/avalanchego/utils/crypto/bls"
+	avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+	"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
+	"github.com/ava-labs/coreth/utils"
+	"github.com/ava-labs/coreth/warp/warptest"
+	"github.com/stretchr/testify/require"
+	"google.golang.org/protobuf/proto"
+)
+
+func TestAddressedCallSignatures(t *testing.T) {
+	database := memdb.New()
+	snowCtx := utils.TestSnowContext()
+	blsSecretKey, err := bls.NewSecretKey()
+	require.NoError(t, err)
+	warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID)
+
+	offChainPayload, err := payload.NewAddressedCall([]byte{1, 2, 3}, []byte{1, 2, 3})
+	require.NoError(t, err)
+	offchainMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, offChainPayload.Bytes())
+	require.NoError(t, err)
+	offchainSignature, err := warpSigner.Sign(offchainMessage)
+	require.NoError(t, err)
+
+	tests := map[string]struct {
+		setup       func(backend Backend) (request []byte, expectedResponse []byte)
+		verifyStats func(t *testing.T, stats *verifierStats)
+		err         error
+	}{
+		"known message": {
+			setup: func(backend Backend) (request []byte, expectedResponse []byte) {
+				knownPayload, err := payload.NewAddressedCall([]byte{0, 0, 0}, []byte("test"))
+				require.NoError(t, err)
+				msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, knownPayload.Bytes())
+				require.NoError(t, err)
+				signature, err := warpSigner.Sign(msg)
+				require.NoError(t, err)
+
+				backend.AddMessage(msg)
+				return msg.Bytes(), signature[:]
+			},
+			verifyStats: func(t *testing.T, stats *verifierStats) {
+				require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count())
+				require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count())
+			},
+		},
+		"offchain message": {
+			setup: func(_ Backend) (request []byte, expectedResponse []byte) {
+				return offchainMessage.Bytes(), offchainSignature[:]
+			},
+			verifyStats: func(t *testing.T, stats *verifierStats) {
+				require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count())
+				require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count())
+			},
+		},
+		"unknown message": {
+			setup: func(_ Backend) (request []byte, expectedResponse []byte) {
+				unknownPayload, err := payload.NewAddressedCall([]byte{0, 0, 0}, []byte("unknown message"))
+				require.NoError(t, err)
+				unknownMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, unknownPayload.Bytes())
+				require.NoError(t, err)
+				return unknownMessage.Bytes(), nil
+			},
+			verifyStats: func(t *testing.T, stats *verifierStats) {
+				require.EqualValues(t, 1, stats.messageParseFail.Snapshot().Count())
+				require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count())
+			},
+			err: &common.AppError{Code: ParseErrCode},
+		},
+	}
+
+	for name, test := range tests {
+		for _, withCache := range []bool{true, false} {
+			if withCache {
+				name += "_with_cache"
+			} else {
+				name += "_no_cache"
+			}
+			t.Run(name, func(t *testing.T) {
+				var sigCache cache.Cacher[ids.ID, []byte]
+				if withCache {
+					sigCache = &cache.LRU[ids.ID, []byte]{Size: 100}
+				} else {
+					sigCache = &cache.Empty[ids.ID, []byte]{}
+				}
+				warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, sigCache, [][]byte{offchainMessage.Bytes()})
+				require.NoError(t, err)
+				handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner)
+
+				requestBytes, expectedResponse := test.setup(warpBackend)
+				protoMsg := &sdk.SignatureRequest{Message: requestBytes}
+				protoBytes, err := proto.Marshal(protoMsg)
+				require.NoError(t, err)
+				responseBytes, appErr := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, protoBytes)
+				if test.err != nil {
+					require.Error(t, appErr)
+					require.ErrorIs(t, appErr, test.err)
+				} else {
+					require.Nil(t, appErr)
+				}
+
+				test.verifyStats(t, warpBackend.(*backend).stats)
+
+				// If the expected response is empty, assert that the handler returns an empty response and return early.
+				if len(expectedResponse) == 0 {
+					require.Len(t, responseBytes, 0, "expected response to be empty")
+					return
+				}
+				// check cache is populated
+				if withCache {
+					require.NotZero(t, warpBackend.(*backend).signatureCache.Len())
+				} else {
+					require.Zero(t, warpBackend.(*backend).signatureCache.Len())
+				}
+				response := &sdk.SignatureResponse{}
+				require.NoError(t, proto.Unmarshal(responseBytes, response))
+				require.NoError(t, err, "error unmarshalling SignatureResponse")
+
+				require.Equal(t, expectedResponse, response.Signature)
+			})
+		}
+	}
+}
+
+func TestBlockSignatures(t *testing.T) {
+	database := memdb.New()
+	snowCtx := utils.TestSnowContext()
+	blsSecretKey, err := bls.NewSecretKey()
+	require.NoError(t, err)
+
+	warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID)
+	knownBlkID := ids.GenerateTestID()
+	blockClient := warptest.MakeBlockClient(knownBlkID)
+
+	toMessageBytes := func(id ids.ID) []byte {
+		idPayload, err := payload.NewHash(id)
+		if err != nil {
+			panic(err)
+		}
+
+		msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, idPayload.Bytes())
+		if err != nil {
+			panic(err)
+		}
+
+		return msg.Bytes()
+	}
+
+	tests := map[string]struct {
+		setup       func() (request []byte, expectedResponse []byte)
+		verifyStats func(t *testing.T, stats *verifierStats)
+		err         error
+	}{
+		"known block": {
+			setup: func() (request []byte, expectedResponse []byte) {
+				hashPayload, err := payload.NewHash(knownBlkID)
+				require.NoError(t, err)
+				unsignedMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, hashPayload.Bytes())
+				require.NoError(t, err)
+				signature, err := warpSigner.Sign(unsignedMessage)
+				require.NoError(t, err)
+				return toMessageBytes(knownBlkID), signature[:]
+			},
+			verifyStats: func(t *testing.T, stats *verifierStats) {
+				require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count())
+				require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count())
+			},
+		},
+		"unknown block": {
+			setup: func() (request []byte, expectedResponse []byte) {
+				unknownBlockID := ids.GenerateTestID()
+				return toMessageBytes(unknownBlockID), nil
+			},
+			verifyStats: func(t *testing.T, stats *verifierStats) {
+				require.EqualValues(t, 1, stats.blockSignatureValidationFail.Snapshot().Count())
+				require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count())
+			},
+			err: &common.AppError{Code: VerifyErrCode},
+		},
+	}
+
+	for name, test := range tests {
+		for _, withCache := range []bool{true, false} {
+			if withCache {
+				name += "_with_cache"
+			} else {
+				name += "_no_cache"
+			}
+			t.Run(name, func(t *testing.T) {
+				var sigCache cache.Cacher[ids.ID, []byte]
+				if withCache {
+					sigCache = &cache.LRU[ids.ID, []byte]{Size: 100}
+				} else {
+					sigCache = &cache.Empty[ids.ID, []byte]{}
+				}
+				warpBackend, err := NewBackend(
+					snowCtx.NetworkID,
+					snowCtx.ChainID,
+					warpSigner,
+					blockClient,
+					database,
+					sigCache,
+					nil,
+				)
+				require.NoError(t, err)
+				handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner)
+
+				requestBytes, expectedResponse := test.setup()
+				protoMsg := &sdk.SignatureRequest{Message: requestBytes}
+				protoBytes, err := proto.Marshal(protoMsg)
+				require.NoError(t, err)
+				responseBytes, appErr := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, protoBytes)
+				if test.err != nil {
+					require.NotNil(t, appErr)
+					require.ErrorIs(t, test.err, appErr)
+				} else {
+					require.Nil(t, appErr)
+				}
+
+				test.verifyStats(t, warpBackend.(*backend).stats)
+
+				// If the expected response is empty, assert that the handler returns an empty response and return early.
+				if len(expectedResponse) == 0 {
+					require.Len(t, responseBytes, 0, "expected response to be empty")
+					return
+				}
+				// check cache is populated
+				if withCache {
+					require.NotZero(t, warpBackend.(*backend).signatureCache.Len())
+				} else {
+					require.Zero(t, warpBackend.(*backend).signatureCache.Len())
+				}
+				var response sdk.SignatureResponse
+				err = proto.Unmarshal(responseBytes, &response)
+				require.NoError(t, err, "error unmarshalling SignatureResponse")
+				require.Equal(t, expectedResponse, response.Signature)
+			})
+		}
+	}
+}
diff --git a/warp/verifier_stats.go b/warp/verifier_stats.go
new file mode 100644
index 0000000000..3ee90312d9
--- /dev/null
+++ b/warp/verifier_stats.go
@@ -0,0 +1,29 @@
+// (c) 2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package warp
+
+import (
+	"github.com/ava-labs/coreth/metrics"
+)
+
+type verifierStats struct {
+	messageParseFail metrics.Counter
+	// BlockRequest metrics
+	blockSignatureValidationFail metrics.Counter
+}
+
+func newVerifierStats() *verifierStats {
+	return &verifierStats{
+		messageParseFail:             metrics.NewRegisteredCounter("message_parse_fail", nil),
+		blockSignatureValidationFail: metrics.NewRegisteredCounter("block_signature_validation_fail", nil),
+	}
+}
+
+func (h *verifierStats) IncBlockSignatureValidationFail() {
+	h.blockSignatureValidationFail.Inc(1)
+}
+
+func (h *verifierStats) IncMessageParseFail() {
+	h.messageParseFail.Inc(1)
+}