diff --git a/README.md b/README.md index efa120a578..a0058a9844 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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. -Subnet EVM is the [Virtual Machine (VM)](https://docs.avax.network/learn/avalanche/virtual-machines) that defines the Subnet Contract Chains. Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). +Subnet EVM is the [Virtual Machine (VM)](https://docs.avax.network/learn/virtual-machines) that defines the Subnet Contract Chains. Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). This chain implements the Ethereum Virtual Machine and supports Solidity smart contracts as well as most other Ethereum client functionality. diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index 93b03be810..1dfff2f1f5 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -125,10 +125,12 @@ func TestWaitDeployedCornerCases(t *testing.T) { // Create a transaction to an account. code := "6060604052600a8060106000396000f360606040526008565b00" tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.LatestSigner(params.TestChainConfig), testKey) + tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - backend.Client().SendTransaction(ctx, tx) + if err := backend.Client().SendTransaction(ctx, tx); err != nil { + t.Fatalf("Failed to send transaction: %s", err) + } backend.Commit(true) notContractCreation := errors.New("tx is not contract creation") if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() { @@ -137,7 +139,7 @@ func TestWaitDeployedCornerCases(t *testing.T) { // Create a transaction that is not mined. tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.LatestSigner(params.TestChainConfig), testKey) + tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey) go func() { contextCanceled := errors.New("context canceled") @@ -146,6 +148,8 @@ func TestWaitDeployedCornerCases(t *testing.T) { } }() - backend.Client().SendTransaction(ctx, tx) + if err := backend.Client().SendTransaction(ctx, tx); err != nil { + t.Fatalf("Failed to send transaction: %s", err) + } cancel() } diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index f44e01d7ac..29eb68b5a2 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -324,7 +324,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, statedb.AddBalance(pre.Env.Coinbase, uint256.MustFromBig(minerReward)) } // Commit block - root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), false) + root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } @@ -366,7 +366,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) + root, _ := statedb.Commit(0, false) statedb, _ = state.New(root, sdb, nil) return statedb } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 48902609ba..2ff5bd962d 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -281,7 +281,7 @@ func runCmd(ctx *cli.Context) error { output, leftOverGas, stats, err := timedExec(bench, execFunc) if ctx.Bool(DumpFlag.Name) { - statedb.Commit(genesisConfig.Number, true, false) + statedb.Commit(genesisConfig.Number, true) fmt.Println(string(statedb.Dump(nil))) } diff --git a/core/blockchain.go b/core/blockchain.go index a1272336e3..7fc6c62688 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -196,10 +196,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 { @@ -1222,9 +1223,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 @@ -1756,9 +1757,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] @@ -1899,8 +1900,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 ad5c106cf3..6b8467dc3c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -300,7 +300,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 1ee615a59a..e08f186981 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -328,7 +328,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 e0c3cb41ac..24313429db 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) @@ -127,7 +127,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{} @@ -153,7 +153,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) @@ -225,7 +225,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 9779bec6fe..e73cf9accb 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1217,14 +1217,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 @@ -1234,7 +1234,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) @@ -1343,14 +1343,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 47b22b104e..7bd3c0b602 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 60d7ee3b41..29e91111ab 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -126,7 +126,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) } @@ -134,7 +134,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) } @@ -542,7 +542,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() @@ -630,7 +630,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) @@ -744,7 +744,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) @@ -778,7 +778,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 @@ -790,7 +790,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 { @@ -833,7 +833,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) @@ -857,7 +857,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) } @@ -1053,7 +1053,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) } @@ -1132,7 +1132,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) @@ -1163,7 +1163,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/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 27bf9f0eab..b7d0c06c2b 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -583,7 +583,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, @@ -702,7 +702,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, @@ -804,7 +804,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, @@ -884,7 +884,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, @@ -1302,7 +1302,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 @@ -1375,7 +1375,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 b4b626104c..218f686337 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{ @@ -192,7 +192,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 0508e7dc56..89876b182a 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/internal/debug/flags.go b/internal/debug/flags.go index 34289ab921..dfb23fa863 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -38,7 +38,6 @@ import ( "github.com/ava-labs/subnet-evm/internal/flags" "github.com/ethereum/go-ethereum/log" - "github.com/fjl/memsize/memsizeui" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" @@ -46,8 +45,6 @@ import ( "gopkg.in/natefinch/lumberjack.v2" ) -var Memsize memsizeui.Handler - var ( verbosityFlag = &cli.IntFlag{ Name: "verbosity", @@ -314,7 +311,6 @@ func Setup(ctx *cli.Context) error { } func StartPProf(address string) { - http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) go func() { if err := http.ListenAndServe(address, nil); err != nil { diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index eb235379ee..abaf68f4fc 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -126,10 +126,9 @@ func TestSendWarpMessage(t *testing.T) { logData := receipts[0].Logs[0].Data unsignedMessage, err := warp.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(unsignedMessage) require.Error(err) _, err = vm.warpBackend.GetBlockSignature(blk.ID()) require.Error(err) @@ -139,7 +138,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(unsignedMessage) require.NoError(err) blsSignature, err := bls.SignatureFromBytes(rawSignatureBytes[:]) require.NoError(err) @@ -750,7 +749,7 @@ 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(warpMessage) require.NoError(t, err) tests := map[string]struct { diff --git a/scripts/known_flakes.txt b/scripts/known_flakes.txt index e6652a59b4..6f39599d6c 100644 --- a/scripts/known_flakes.txt +++ b/scripts/known_flakes.txt @@ -8,5 +8,6 @@ TestResumeSyncAccountsTrieInterrupted TestResyncNewRootAfterDeletes TestTransactionSkipIndexing TestVMShutdownWhileSyncing +TestWaitDeployedCornerCases TestWalletNotifications TestWebsocketLargeRead \ No newline at end of file diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 7ba439accc..b6a59bb9b1 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -327,7 +327,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh state.StateDB.AddBalance(block.Coinbase(), new(uint256.Int)) // Commit state mutations into database. - root, _ = state.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number()), false) + root, _ = state.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number())) return state, root, err } @@ -475,7 +475,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 5383b57540..8042110275 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 36b794abf2..fc513c0c50 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/warp/backend.go b/warp/backend.go index 7e7377ad57..360161a336 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -15,6 +15,7 @@ import ( "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/subnet-evm/warp/messages" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) @@ -36,8 +37,8 @@ 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 returns the signature of the requested message. + GetMessageSignature(message *avalancheWarp.UnsignedMessage) ([bls.SignatureLen]byte, error) // GetBlockSignature returns the signature of the requested message hash. GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) @@ -142,15 +143,16 @@ func (b *backend) AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) err return nil } -func (b *backend) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) { +func (b *backend) GetMessageSignature(unsignedMessage *avalancheWarp.UnsignedMessage) ([bls.SignatureLen]byte, error) { + messageID := unsignedMessage.ID() + log.Debug("Getting warp message from backend", "messageID", messageID) if sig, ok := b.messageSignatureCache.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.ValidateMessage(unsignedMessage); err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to validate warp message: %w", err) } var signature [bls.SignatureLen]byte @@ -164,6 +166,37 @@ func (b *backend) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, return signature, nil } +func (b *backend) ValidateMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error { + // Known on-chain messages should be signed + if _, err := b.GetMessage(unsignedMessage.ID()); err == nil { + return nil + } + + // Try to parse the payload as an AddressedCall + addressedCall, err := payload.ParseAddressedCall(unsignedMessage.Payload) + if err != nil { + return fmt.Errorf("failed to parse unknown message as AddressedCall: %w", err) + } + + // Further, parse the payload to see if it is a known type. + parsed, err := messages.Parse(addressedCall.Payload) + if err != nil { + return fmt.Errorf("failed to parse unknown message: %w", err) + } + + // Check if the message is a known type that can be signed on demand + signable, ok := parsed.(messages.Signable) + if !ok { + return fmt.Errorf("parsed message is not Signable: %T", signable) + } + + // Check if the message should be signed according to its type + if err := signable.VerifyMesssage(addressedCall.SourceAddress); err != nil { + return fmt.Errorf("failed to verify Signable message: %w", err) + } + return nil +} + func (b *backend) GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) { log.Debug("Getting block from backend", "blockID", blockID) if sig, ok := b.blockSignatureCache.Get(blockID); ok { diff --git a/warp/backend_test.go b/warp/backend_test.go index a262d760ef..21013dfc24 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -10,7 +10,6 @@ import ( "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/subnet-evm/warp/warptest" @@ -49,18 +48,17 @@ func TestClearDB(t *testing.T) { // 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{} + messages := make([]*avalancheWarp.UnsignedMessage, 0, len(payloads)) // 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) + messages = append(messages, unsignedMsg) err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // ensure that the message was added - _, err = backend.GetMessageSignature(messageID) + _, err = backend.GetMessageSignature(unsignedMsg) require.NoError(t, err) } @@ -74,9 +72,9 @@ func TestClearDB(t *testing.T) { 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") + for _, message := range messages { + _, err := backend.GetMessageSignature(message) + require.ErrorContains(t, err, "failed to validate warp message") } } @@ -94,8 +92,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(testUnsignedMessage) require.NoError(t, err) expectedSig, err := warpSigner.Sign(testUnsignedMessage) @@ -113,8 +110,7 @@ func TestAddAndGetUnknownMessage(t *testing.T) { 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(testUnsignedMessage) require.Error(t, err) } @@ -162,8 +158,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(testUnsignedMessage) require.NoError(t, err) expectedSig, err := warpSigner.Sign(testUnsignedMessage) @@ -192,7 +187,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(testUnsignedMessage) require.NoError(err) expectedSignatureBytes, err := warpSigner.Sign(msg) require.NoError(err) diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go index cab7914243..3a28cd994e 100644 --- a/warp/handlers/signature_request.go +++ b/warp/handlers/signature_request.go @@ -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() + signature, err = s.backend.GetMessageSignature(unsignedMessage) + if err != nil { + log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID) + s.stats.IncMessageSignatureMiss() + signature = [bls.SignatureLen]byte{} + } else { + s.stats.IncMessageSignatureHit() + } } response := message.SignatureResponse{Signature: signature} diff --git a/warp/handlers/signature_request_p2p.go b/warp/handlers/signature_request_p2p.go index 0728f8b808..cb711974b2 100644 --- a/warp/handlers/signature_request_p2p.go +++ b/warp/handlers/signature_request_p2p.go @@ -28,6 +28,10 @@ const ( ErrFailedToMarshal ) +type AddressedCallHandler interface { + GetMessageSignature(*avalancheWarp.UnsignedMessage) ([bls.SignatureLen]byte, error) +} + // SignatureRequestHandlerP2P serves warp signature requests using the p2p // framework from avalanchego. It is a peer.RequestHandler for // message.MessageSignatureRequest. @@ -79,11 +83,7 @@ func (s *SignatureRequestHandlerP2P) AppRequest( 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()) + sig, err = s.GetMessageSignature(unsignedMessage) if err != nil { s.stats.IncMessageSignatureMiss() } else { @@ -122,7 +122,7 @@ func (s *SignatureRequestHandlerP2P) AppRequest( return respBytes, nil } -func (s *SignatureRequestHandlerP2P) GetMessageSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) { +func (s *SignatureRequestHandlerP2P) GetMessageSignature(message *avalancheWarp.UnsignedMessage) ([bls.SignatureLen]byte, error) { startTime := time.Now() s.stats.IncMessageSignatureRequest() @@ -131,7 +131,7 @@ func (s *SignatureRequestHandlerP2P) GetMessageSignature(messageID ids.ID) ([bls s.stats.UpdateMessageSignatureRequestTime(time.Since(startTime)) }() - return s.backend.GetMessageSignature(messageID) + return s.backend.GetMessageSignature(message) } func (s *SignatureRequestHandlerP2P) GetBlockSignature(blockID ids.ID) ([bls.SignatureLen]byte, error) { diff --git a/warp/handlers/signature_request_p2p_test.go b/warp/handlers/signature_request_p2p_test.go index 1f8f9530cb..3104fe59b3 100644 --- a/warp/handlers/signature_request_p2p_test.go +++ b/warp/handlers/signature_request_p2p_test.go @@ -42,11 +42,10 @@ func TestMessageSignatureHandlerP2P(t *testing.T) { 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) + signature, err := backend.GetMessageSignature(msg) require.NoError(t, err) - offchainSignature, err := backend.GetMessageSignature(offchainMessage.ID()) + offchainSignature, err := backend.GetMessageSignature(offchainMessage) require.NoError(t, err) unknownPayload, err := payload.NewAddressedCall([]byte{0, 0, 0}, []byte("unknown message")) diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index 172f182c96..1f699324cc 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -38,9 +38,9 @@ func TestMessageSignatureHandler(t *testing.T) { require.NoError(t, err) messageID := msg.ID() require.NoError(t, backend.AddMessage(msg)) - signature, err := backend.GetMessageSignature(messageID) + signature, err := backend.GetMessageSignature(msg) require.NoError(t, err) - offchainSignature, err := backend.GetMessageSignature(offchainMessage.ID()) + offchainSignature, err := backend.GetMessageSignature(offchainMessage) require.NoError(t, err) unknownMessageID := ids.GenerateTestID() diff --git a/warp/messages/codec.go b/warp/messages/codec.go new file mode 100644 index 0000000000..87d2fa334a --- /dev/null +++ b/warp/messages/codec.go @@ -0,0 +1,33 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package messages + +import ( + "errors" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/utils/units" +) + +const ( + CodecVersion = 0 + + MaxMessageSize = 24 * units.KiB +) + +var Codec codec.Manager + +func init() { + Codec = codec.NewManager(MaxMessageSize) + lc := linearcodec.NewDefault() + + err := errors.Join( + lc.RegisterType(&ValidatorUptime{}), + Codec.RegisterCodec(CodecVersion, lc), + ) + if err != nil { + panic(err) + } +} diff --git a/warp/messages/payload.go b/warp/messages/payload.go new file mode 100644 index 0000000000..3776a1356d --- /dev/null +++ b/warp/messages/payload.go @@ -0,0 +1,45 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package messages + +import ( + "errors" + "fmt" +) + +var errWrongType = errors.New("wrong payload type") + +// Payload provides a common interface for all payloads implemented by this +// package. +type Payload interface { + // Bytes returns the binary representation of this payload. + Bytes() []byte + + // initialize the payload with the provided binary representation. + initialize(b []byte) +} + +// Signable is an optional interface that payloads can implement to allow +// on-the-fly signing of incoming messages by the warp backend. +type Signable interface { + VerifyMesssage(sourceAddress []byte) error +} + +func Parse(bytes []byte) (Payload, error) { + var payload Payload + if _, err := Codec.Unmarshal(bytes, &payload); err != nil { + return nil, err + } + payload.initialize(bytes) + return payload, nil +} + +func initialize(p Payload) error { + bytes, err := Codec.Marshal(CodecVersion, &p) + if err != nil { + return fmt.Errorf("couldn't marshal %T payload: %w", p, err) + } + p.initialize(bytes) + return nil +} diff --git a/warp/messages/validator_uptime.go b/warp/messages/validator_uptime.go new file mode 100644 index 0000000000..3d3e4dd5dd --- /dev/null +++ b/warp/messages/validator_uptime.go @@ -0,0 +1,51 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package messages + +import ( + "fmt" + + "github.com/ava-labs/avalanchego/ids" +) + +// ValidatorUptime is signed when the ValidationID is known and the validator +// has been up for TotalUptime seconds. +type ValidatorUptime struct { + ValidationID ids.ID `serialize:"true"` + TotalUptime uint64 `serialize:"true"` + + bytes []byte +} + +// NewValidatorUptime creates a new *ValidatorUptime and initializes it. +func NewValidatorUptime(validationID ids.ID, totalUptime uint64) (*ValidatorUptime, error) { + bhp := &ValidatorUptime{ + ValidationID: validationID, + TotalUptime: totalUptime, + } + return bhp, initialize(bhp) +} + +// ParseValidatorUptime converts a slice of bytes into an initialized ValidatorUptime. +func ParseValidatorUptime(b []byte) (*ValidatorUptime, error) { + payloadIntf, err := Parse(b) + if err != nil { + return nil, err + } + payload, ok := payloadIntf.(*ValidatorUptime) + if !ok { + return nil, fmt.Errorf("%w: %T", errWrongType, payloadIntf) + } + return payload, nil +} + +// Bytes returns the binary representation of this payload. It assumes that the +// payload is initialized from either NewValidatorUptime or Parse. +func (b *ValidatorUptime) Bytes() []byte { + return b.bytes +} + +func (b *ValidatorUptime) initialize(bytes []byte) { + b.bytes = bytes +} diff --git a/warp/service.go b/warp/service.go index 4afe93a168..2472c63b09 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(unsignedMessage) if err != nil { return nil, fmt.Errorf("failed to get signature for message %s with error %w", messageID, err) }