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) +}