diff --git a/cmd/juno/verify/trie.go b/cmd/juno/verify/trie.go new file mode 100644 index 0000000000..4aa980666c --- /dev/null +++ b/cmd/juno/verify/trie.go @@ -0,0 +1,534 @@ +package verify + +import ( + "bytes" + "context" + "errors" + "fmt" + "slices" + "sync" + "time" + + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/utils" + "github.com/spf13/cobra" +) + +const ( + starknetTrieHeight = 251 + concurrencyMaxDepth = 8 + verifyContractAddr = "address" +) + +type TrieType string + +const ( + ContractTrieType TrieType = "contract" + ClassTrieType TrieType = "class" + ContractStorageTrieType TrieType = "contract-storage" +) + +func verifyTrieCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "trie", + Short: "Verify trie integrity", + Long: `Verify trie integrity by rebuilding tries from leaf nodes and comparing root hashes.`, + RunE: runTrieVerify, + } + + cmd.Flags().StringSlice( + verifyTrieType, + nil, + "Trie types to verify (contract, class, contract-storage). Can be specified multiple times. Empty = all.", + ) + + cmd.Flags().String( + verifyContractAddr, + "", + "Contract address to verify (only used with --type contract-storage). If not specified, all contract storage tries are verified.", + ) + + return cmd +} + +func runTrieVerify(cmd *cobra.Command, args []string) error { + dbPath, err := cmd.Flags().GetString(verifyDBPathF) + if err != nil { + return err + } + + database, err := openDB(dbPath) + if err != nil { + return err + } + defer database.Close() + + trieTypes, err := cmd.Flags().GetStringSlice(verifyTrieType) + if err != nil { + return err + } + + contractAddrStr, err := cmd.Flags().GetString(verifyContractAddr) + if err != nil { + return err + } + + cfg := &TrieConfig{} + + if len(trieTypes) > 0 { + cfg.Tries = make([]TrieType, len(trieTypes)) + for i, t := range trieTypes { + cfg.Tries[i] = TrieType(t) + } + } + + if contractAddrStr != "" { + hasContractStorage := slices.Contains(cfg.Tries, ContractStorageTrieType) + if len(cfg.Tries) == 0 { + hasContractStorage = true + } + + if !hasContractStorage { + return fmt.Errorf("--address flag can only be used with --type contract-storage") + } + + var contractAddr felt.Felt + _, err := (&contractAddr).SetString(contractAddrStr) + if err != nil { + return fmt.Errorf("invalid contract address %s: %w", contractAddrStr, err) + } + cfg.ContractAddress = &contractAddr + } + + logLevel := utils.NewLogLevel(utils.INFO) + logger, err := utils.NewZapLogger(logLevel, true) + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + verifier := NewTrieVerifier(database, logger) + ctx := cmd.Context() + return verifier.Run(ctx, cfg) +} + +type TrieConfig struct { + Tries []TrieType + ContractAddress *felt.Felt +} + +type TrieVerifier struct { + database db.KeyValueStore + logger utils.SimpleLogger +} + +func NewTrieVerifier(database db.KeyValueStore, logger utils.SimpleLogger) *TrieVerifier { + return &TrieVerifier{ + database: database, + logger: logger, + } +} + +func (v *TrieVerifier) Name() string { + return "trie" +} + +func (v *TrieVerifier) DefaultConfig() Config { + return &TrieConfig{ + Tries: nil, + } +} + +type TrieInfo struct { + Name string + prefix []byte + HashFunc trie.NewTrieFunc + HashFn crypto.HashFn + ReaderFunc func(db.KeyValueReader, []byte, uint8) (trie.TrieReader, error) + Height uint8 +} + +func (v *TrieVerifier) Run(ctx context.Context, cfg Config) error { + startTime := time.Now() + defer func() { + elapsed := time.Since(startTime) + v.logger.Infow("=== Trie verification finished ===", "total_elapsed", elapsed.Round(time.Second)) + }() + + trieCfg, ok := cfg.(*TrieConfig) + if !ok { + return fmt.Errorf("invalid config type for trie verifier: expected *TrieConfig") + } + + typesToVerify := trieCfg.Tries + if len(typesToVerify) == 0 { + typesToVerify = []TrieType{ContractTrieType, ClassTrieType, ContractStorageTrieType} + } + + typeSet := make(map[TrieType]bool) + for _, t := range typesToVerify { + typeSet[t] = true + } + + if typeSet[ContractTrieType] { + stateTrieInfo := TrieInfo{ + Name: "ContractsTrie", + prefix: db.StateTrie.Key(), + HashFunc: trie.NewTriePedersen, + HashFn: crypto.Pedersen, + ReaderFunc: trie.NewTrieReaderPedersen, + Height: starknetTrieHeight, + } + if err := v.verifyTrieWithLogging(ctx, stateTrieInfo); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return err + } + } + + if typeSet[ClassTrieType] { + classTrieInfo := TrieInfo{ + Name: "ClassesTrie", + prefix: db.ClassesTrie.Key(), + HashFunc: trie.NewTriePoseidon, + HashFn: crypto.Poseidon, + ReaderFunc: trie.NewTrieReaderPoseidon, + Height: starknetTrieHeight, + } + if err := v.verifyTrieWithLogging(ctx, classTrieInfo); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return err + } + } + + if typeSet[ContractStorageTrieType] { + if err := v.verifyContractStorageTries(ctx, trieCfg.ContractAddress); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return err + } + } + + v.logger.Infow("=== Trie verification completed successfully ===") + return nil +} + +func (v *TrieVerifier) collectContractAddresses() []felt.Felt { + contractAddresses := make([]felt.Felt, 0) + stateTriePrefix := db.StateTrie.Key() + + err := v.database.View(func(snap db.Snapshot) error { + it, err := snap.NewIterator(stateTriePrefix, true) + if err != nil { + return err + } + defer it.Close() + + for it.First(); it.Valid(); it.Next() { + keyBytes := it.Key() + if bytes.Equal(keyBytes, stateTriePrefix) { + continue + } + + if !bytes.HasPrefix(keyBytes, stateTriePrefix) { + continue + } + nodeKeyBytes := keyBytes[len(stateTriePrefix):] + + var nodeKey trie.BitArray + if err := nodeKey.UnmarshalBinary(nodeKeyBytes); err != nil { + continue + } + + if nodeKey.Len() == starknetTrieHeight { + contractAddr := nodeKey.Felt() + contractAddresses = append(contractAddresses, contractAddr) + } + } + return nil + }) + if err != nil { + return nil + } + + return contractAddresses +} + +func (v *TrieVerifier) verifyTrieWithLogging(ctx context.Context, trieInfo TrieInfo) error { + err := v.verifyTrie(ctx, trieInfo) + if err != nil { + if errors.Is(err, context.Canceled) { + v.logger.Infow("Verification stopped", "trie", trieInfo.Name) + return err + } + v.logger.Errorw(fmt.Sprintf("%s verification failed", trieInfo.Name), "error", err) + return fmt.Errorf("%s verification failed: %w", trieInfo.Name, err) + } + return nil +} + +func (v *TrieVerifier) verifyTrie(ctx context.Context, trieInfo TrieInfo) error { + v.logger.Infow(fmt.Sprintf("=== Starting %s verification ===", trieInfo.Name)) + expectedRoot := felt.Zero + err := v.database.View(func(snap db.Snapshot) error { + reader, err := trieInfo.ReaderFunc(snap, trieInfo.prefix, trieInfo.Height) + if err != nil { + return err + } + if reader.RootKey() == nil { + expectedRoot = felt.Zero + return nil + } + expectedRoot, err = reader.Hash() + return err + }) + if err != nil { + v.logger.Errorw("Failed to get stored root hash", "trie", trieInfo.Name, "error", err) + return fmt.Errorf("failed to get stored root hash for %s: %w", trieInfo.Name, err) + } + + if expectedRoot.IsZero() { + v.logger.Infow("Trie is empty (zero root)", "trie", trieInfo.Name) + return nil + } + + v.logger.Infow("Starting verification", + "trie", + trieInfo.Name, + "expectedRoot", + expectedRoot.String(), + ) + storageReader := trie.NewReadStorage(v.database, trieInfo.prefix) + + err = verifyTrie(ctx, storageReader, starknetTrieHeight, trieInfo.HashFn, &expectedRoot) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + v.logger.Errorw("Trie verification failed", "trie", trieInfo.Name, "error", err) + return err + } + + v.logger.Infow("Trie verification successful", + "trie", trieInfo.Name, "root", + expectedRoot.String(), + ) + return nil +} + +func (v *TrieVerifier) verifyContractStorageTries(ctx context.Context, filterAddress *felt.Felt) error { + v.logger.Infow("=== Starting Contract Storage Tries verification ===") + + var contractAddresses []felt.Felt + if filterAddress != nil { + contractAddresses = []felt.Felt{*filterAddress} + v.logger.Infow("Verifying specific contract", "address", filterAddress.String()) + } else { + contractAddresses = v.collectContractAddresses() + if len(contractAddresses) == 0 { + v.logger.Infow("No contract addresses found, skipping contract storage verification") + return nil + } + v.logger.Infow("Found contracts to verify", "count", len(contractAddresses)) + } + + for i, contractAddress := range contractAddresses { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + addrBytes := contractAddress.Marshal() + prefix := db.ContractStorage.Key(addrBytes) + trieInfo := TrieInfo{ + Name: fmt.Sprintf("ContractStorage[%s]", contractAddress.String()), + prefix: db.ContractStorage.Key(addrBytes), + HashFunc: trie.NewTriePedersen, + HashFn: crypto.Pedersen, + ReaderFunc: func(r db.KeyValueReader, _ []byte, height uint8) (trie.TrieReader, error) { + return trie.NewTrieReaderPedersen(r, prefix, height) + }, + Height: starknetTrieHeight, + } + + v.logger.Infow( + "Verifying contract storage", + "contract", + contractAddress.String(), + "progress", + fmt.Sprintf("%d/%d", i+1, len(contractAddresses)), + ) + + err := v.verifyTrie(ctx, trieInfo) + if err != nil { + if errors.Is(err, context.Canceled) { + return err + } + v.logger.Errorw( + "Contract storage verification failed", + "contract", + contractAddress.String(), + "error", + err, + ) + return fmt.Errorf( + "contract storage verification failed for %s: %w", + contractAddress.String(), err, + ) + } + } + + v.logger.Infow("All contract storage tries verified successfully", "count", len(contractAddresses)) + return nil +} + +func verifyTrie( + ctx context.Context, + reader *trie.ReadStorage, + height uint8, + hashFn crypto.HashFn, + expectedRoot *felt.Felt, +) error { + rootKey, err := reader.RootKey() + if err != nil { + return fmt.Errorf("failed to get root key: %w", err) + } + + if rootKey == nil { + return nil + } + + startTime := time.Now() + rootHash, err := verifyNode(ctx, reader, rootKey, nil, height, hashFn) + if err != nil { + return fmt.Errorf("node verification failed: %w", err) + } + + elapsed := time.Since(startTime) + + if rootHash.Cmp(expectedRoot) != 0 { + return fmt.Errorf( + "root hash mismatch: expected %s, got %s (verification took %v)", + expectedRoot, rootHash, elapsed.Round(time.Second), + ) + } + + return nil +} + +func verifyNode( + ctx context.Context, + reader *trie.ReadStorage, + key *trie.BitArray, + parentKey *trie.BitArray, + height uint8, + hashFn crypto.HashFn, +) (*felt.Felt, error) { + select { + case <-ctx.Done(): + return nil, fmt.Errorf("verification cancelled: %w", ctx.Err()) + default: + } + + node, err := reader.Get(key) + if err != nil { + return nil, fmt.Errorf("failed to get node at key %s: %w", key.String(), err) + } + + if key.Len() == height { + p := path(key, parentKey) + h := node.Hash(&p, hashFn) + return &h, nil + } + + useConcurrency := key.Len() <= concurrencyMaxDepth + var leftHash, rightHash *felt.Felt + var leftErr, rightErr error + + if useConcurrency { + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if node.Left.IsEmpty() { + zero := felt.Zero + leftHash = &zero + return + } + h, err := verifyNode(ctx, reader, node.Left, key, height, hashFn) + leftHash = h + leftErr = err + }() + + if node.Right.IsEmpty() { + zero := felt.Zero + rightHash = &zero + } else { + h, err := verifyNode(ctx, reader, node.Right, key, height, hashFn) + rightHash = h + rightErr = err + } + + wg.Wait() + + if leftErr != nil { + return nil, leftErr + } + if rightErr != nil { + return nil, rightErr + } + } else { + if node.Left.IsEmpty() { + zero := felt.Zero + leftHash = &zero + } else { + h, err := verifyNode(ctx, reader, node.Left, key, height, hashFn) + if err != nil { + return nil, err + } + leftHash = h + } + + if node.Right.IsEmpty() { + zero := felt.Zero + rightHash = &zero + } else { + h, err := verifyNode(ctx, reader, node.Right, key, height, hashFn) + if err != nil { + return nil, err + } + rightHash = h + } + } + + recomputed := hashFn(leftHash, rightHash) + if recomputed.Cmp(node.Value) != 0 { + return nil, fmt.Errorf( + "node corruption detected at key %s: stored hash=%s, recomputed hash=%s", + key.String(), node.Value.String(), recomputed.String(), + ) + } + + tmp := *node + tmp.Value = &recomputed + + p := path(key, parentKey) + h := tmp.Hash(&p, hashFn) + return &h, nil +} + +func path(key, parentKey *trie.BitArray) trie.BitArray { + if parentKey == nil { + return key.Copy() + } + + var pathKey trie.BitArray + pathKey.LSBs(key, parentKey.Len()+1) + return pathKey +} diff --git a/cmd/juno/verify/trie_test.go b/cmd/juno/verify/trie_test.go new file mode 100644 index 0000000000..0b8c1834d2 --- /dev/null +++ b/cmd/juno/verify/trie_test.go @@ -0,0 +1,322 @@ +package verify + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/db/memory" + "github.com/NethermindEth/juno/db/pebblev2" + "github.com/NethermindEth/juno/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTrieVerifier_Run_ValidStateTrie(t *testing.T) { + logger := utils.NewNopZapLogger() + testDB := memory.New() + defer testDB.Close() + + prefix := db.StateTrie.Key() + txn := testDB.NewIndexedBatch() + trieStorage := trie.NewStorage(txn, prefix) + + testTrie, err := trie.NewTriePedersen(txn, prefix, starknetTrieHeight) + require.NoError(t, err) + + key1 := felt.NewFromUint64[felt.Felt](1) + value1 := felt.NewFromUint64[felt.Felt](100) + _, err = testTrie.Put(key1, value1) + require.NoError(t, err) + + key2 := felt.NewFromUint64[felt.Felt](2) + value2 := felt.NewFromUint64[felt.Felt](200) + _, err = testTrie.Put(key2, value2) + require.NoError(t, err) + + err = testTrie.Commit() + require.NoError(t, err) + + rootHash, err := testTrie.Hash() + require.NoError(t, err) + + if testTrie.RootKey() != nil { + err = trieStorage.PutRootKey(testTrie.RootKey()) + require.NoError(t, err) + } + + err = txn.Write() + require.NoError(t, err) + + verifier := NewTrieVerifier(testDB, logger) + cfg := &TrieConfig{ + Tries: []TrieType{ContractTrieType}, + } + + ctx := context.Background() + err = verifier.Run(ctx, cfg) + assert.NoError(t, err) + + reader, err := trie.NewTrieReaderPedersen(testDB, prefix, starknetTrieHeight) + require.NoError(t, err) + storedHash, err := reader.Hash() + require.NoError(t, err) + assert.True(t, rootHash.Equal(&storedHash)) +} + +func TestTrieVerifier_Run_ValidClassTrie(t *testing.T) { + logger := utils.NewNopZapLogger() + testDB := memory.New() + defer testDB.Close() + + prefix := db.ClassesTrie.Key() + txn := testDB.NewIndexedBatch() + trieStorage := trie.NewStorage(txn, prefix) + + testTrie, err := trie.NewTriePoseidon(txn, prefix, starknetTrieHeight) + require.NoError(t, err) + + key1 := felt.NewFromUint64[felt.Felt](10) + value1 := felt.NewFromUint64[felt.Felt](1000) + _, err = testTrie.Put(key1, value1) + require.NoError(t, err) + + err = testTrie.Commit() + require.NoError(t, err) + + if testTrie.RootKey() != nil { + err = trieStorage.PutRootKey(testTrie.RootKey()) + require.NoError(t, err) + } + + err = txn.Write() + require.NoError(t, err) + + verifier := NewTrieVerifier(testDB, logger) + cfg := &TrieConfig{ + Tries: []TrieType{ClassTrieType}, + } + + ctx := context.Background() + err = verifier.Run(ctx, cfg) + assert.NoError(t, err) +} + +func TestTrieVerifier_Run_CorruptedTrie(t *testing.T) { + logger := utils.NewNopZapLogger() + testDB := memory.New() + defer testDB.Close() + + prefix := db.StateTrie.Key() + txn := testDB.NewIndexedBatch() + trieStorage := trie.NewStorage(txn, prefix) + + testTrie, err := trie.NewTriePedersen(txn, prefix, starknetTrieHeight) + require.NoError(t, err) + + key1 := felt.NewFromUint64[felt.Felt](1) + value1 := felt.NewFromUint64[felt.Felt](100) + _, err = testTrie.Put(key1, value1) + require.NoError(t, err) + + key2 := felt.NewFromUint64[felt.Felt](2) + value2 := felt.NewFromUint64[felt.Felt](200) + _, err = testTrie.Put(key2, value2) + require.NoError(t, err) + + err = testTrie.Commit() + require.NoError(t, err) + + if testTrie.RootKey() != nil { + err = trieStorage.PutRootKey(testTrie.RootKey()) + require.NoError(t, err) + } + + var nodeKey trie.BitArray + nodeKey.SetFelt(starknetTrieHeight, key1) + + node, err := trieStorage.Get(&nodeKey) + require.NoError(t, err) + require.NotNil(t, node) + require.NotNil(t, node.Value) + + assert.True(t, node.Value.Equal(value1), "Expected value1 but got different value") + + corruptedValue := felt.NewFromUint64[felt.Felt](999999) + node.Value = corruptedValue + + err = trieStorage.Put(&nodeKey, node) + require.NoError(t, err) + + err = txn.Write() + require.NoError(t, err) + + verifier := NewTrieVerifier(testDB, logger) + cfg := &TrieConfig{ + Tries: []TrieType{ContractTrieType}, + } + + ctx := context.Background() + err = verifier.Run(ctx, cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "node corruption detected") +} + +func TestTrieVerifier_Run_MultipleTrieTypes(t *testing.T) { + logger := utils.NewNopZapLogger() + testDB := memory.New() + defer testDB.Close() + + statePrefix := db.StateTrie.Key() + txn := testDB.NewIndexedBatch() + stateTrie, err := trie.NewTriePedersen(txn, statePrefix, starknetTrieHeight) + require.NoError(t, err) + + key1 := felt.NewFromUint64[felt.Felt](1) + value1 := felt.NewFromUint64[felt.Felt](100) + _, err = stateTrie.Put(key1, value1) + require.NoError(t, err) + + err = stateTrie.Commit() + require.NoError(t, err) + + stateStorage := trie.NewStorage(txn, statePrefix) + if stateTrie.RootKey() != nil { + err = stateStorage.PutRootKey(stateTrie.RootKey()) + require.NoError(t, err) + } + + classPrefix := db.ClassesTrie.Key() + classTrie, err := trie.NewTriePoseidon(txn, classPrefix, starknetTrieHeight) + require.NoError(t, err) + + key2 := felt.NewFromUint64[felt.Felt](2) + value2 := felt.NewFromUint64[felt.Felt](200) + _, err = classTrie.Put(key2, value2) + require.NoError(t, err) + + err = classTrie.Commit() + require.NoError(t, err) + + classStorage := trie.NewStorage(txn, classPrefix) + if classTrie.RootKey() != nil { + err = classStorage.PutRootKey(classTrie.RootKey()) + require.NoError(t, err) + } + + err = txn.Write() + require.NoError(t, err) + + verifier := NewTrieVerifier(testDB, logger) + cfg := &TrieConfig{ + Tries: []TrieType{ContractTrieType, ClassTrieType}, + } + + ctx := context.Background() + err = verifier.Run(ctx, cfg) + assert.NoError(t, err) +} + +func TestTrieVerifier_Run_EmptyTrie(t *testing.T) { + logger := utils.NewNopZapLogger() + testDB := memory.New() + defer testDB.Close() + + prefix := db.StateTrie.Key() + txn := testDB.NewIndexedBatch() + trieStorage := trie.NewStorage(txn, prefix) + + testTrie, err := trie.NewTriePedersen(txn, prefix, starknetTrieHeight) + require.NoError(t, err) + + err = testTrie.Commit() + require.NoError(t, err) + + if testTrie.RootKey() != nil { + err = trieStorage.PutRootKey(testTrie.RootKey()) + require.NoError(t, err) + } + + err = txn.Write() + require.NoError(t, err) + + verifier := NewTrieVerifier(testDB, logger) + cfg := &TrieConfig{ + Tries: []TrieType{ContractTrieType}, + } + + ctx := context.Background() + err = verifier.Run(ctx, cfg) + assert.NoError(t, err) +} + +func TestRunTrieVerify_AddressFlagValidation(t *testing.T) { + tests := []struct { + name string + trieTypes []string + address string + expectError bool + expectedErrMsg string + }{ + { + name: "address with contract-storage type should succeed", + trieTypes: []string{"contract-storage"}, + address: "0x123", + expectError: false, + }, + { + name: "address with contract and class types should fail", + trieTypes: []string{"contract", "class"}, + address: "0x123", + expectError: true, + expectedErrMsg: "--address flag can only be used with --type contract-storage", + }, + { + name: "address with no type specified should succeed (default includes contract-storage)", + trieTypes: []string{}, + address: "0x123", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + testDB, err := pebblev2.New(dbPath) + require.NoError(t, err) + testDB.Close() + + parentCmd := VerifyCmd("") + args := []string{"--db-path", dbPath, "trie"} + + for _, trieType := range tt.trieTypes { + args = append(args, "--type", trieType) + } + + if tt.address != "" { + args = append(args, "--address", tt.address) + } + + parentCmd.SetArgs(args) + parentCmd.SetOut(os.Stderr) + parentCmd.SetErr(os.Stderr) + + err = parentCmd.ExecuteContext(context.Background()) + + if tt.expectError { + require.Error(t, err) + if tt.expectedErrMsg != "" { + assert.Contains(t, err.Error(), tt.expectedErrMsg) + } + } else if err != nil { + assert.NotContains(t, err.Error(), "--address flag can only be used with --type contract-storage") + } + }) + } +} diff --git a/cmd/juno/verify/verify.go b/cmd/juno/verify/verify.go index 3b937b1988..c6470ecaf7 100644 --- a/cmd/juno/verify/verify.go +++ b/cmd/juno/verify/verify.go @@ -7,11 +7,13 @@ import ( "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/db/pebblev2" + "github.com/NethermindEth/juno/utils" "github.com/spf13/cobra" ) const ( - verifyDBPathF = "db-path" + verifyDBPathF = "db-path" + verifyTrieType = "type" ) func VerifyCmd(defaultDBPath string) *cobra.Command { @@ -22,6 +24,7 @@ func VerifyCmd(defaultDBPath string) *cobra.Command { } verifyCmd.PersistentFlags().String(verifyDBPathF, defaultDBPath, "Path to the database") + verifyCmd.AddCommand(verifyTrieCmd()) verifyCmd.RunE = verifyAll return verifyCmd @@ -40,8 +43,17 @@ func verifyAll(cmd *cobra.Command, args []string) error { } defer database.Close() + logLevel := utils.NewLogLevel(utils.INFO) + logger, err := utils.NewZapLogger(logLevel, true) + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + trieVerifier := NewTrieVerifier(database, logger) verifier := &VerifyRunner{ - Verifiers: []Verifier{}, + Verifiers: []Verifier{ + trieVerifier, + }, } ctx := cmd.Context()