From b82b8f51397f2111d8a2cbde19ef97f8618caf7e Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 26 Jun 2020 13:11:30 -0500 Subject: [PATCH] different modes (full, state, storage) --- cmd/root.go | 11 +++++--- cmd/validateTrie.go | 63 +++++++++++++++++++++++++++++++++++++++------ pkg/validator.go | 57 +++++++++++++++++++++++++++++++--------- 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7e8a253..3af5518 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -24,10 +24,13 @@ import ( ) var ( - subCommand string - logWithCommand logrus.Entry - rootStr string - cfgFile string + subCommand string + logWithCommand logrus.Entry + stateRootStr string + storageRootStr string + validationType string + contractAddrStr string + cfgFile string ) // rootCmd represents the base command when called without any subcommands diff --git a/cmd/validateTrie.go b/cmd/validateTrie.go index 366208a..13e0740 100644 --- a/cmd/validateTrie.go +++ b/cmd/validateTrie.go @@ -16,7 +16,7 @@ package cmd import ( - "fmt" + "strings" "github.com/ethereum/go-ethereum/common" _ "github.com/lib/pq" //postgres driver @@ -30,7 +30,24 @@ import ( var validateTrieCmd = &cobra.Command{ Use: "validateTrie", Short: "Validate completeness of state data on IPFS", - Long: `This command is used to validate the completeness of the state trie corresponding to a specific state root`, + Long: `This command is used to validate the completeness of state data corresponding specific to a specific root + +It can operate at three levels: + +"full" validates completeness of the entire state corresponding to a provided state root, including both state and storage tries + +./eth-ipfs-state-validator validateTrie --config={path to db config} --type=full --state-root={state root hex string} + + +"state" validates completeness of the state trie corresponding to a provided state root, excluding the storage tries + +./eth-ipfs-state-validator validateTrie --config={path to db config} --type=state --state-root={state root hex string} + + +"storage" validates completeness of only the storage trie corresponding to a provided storage root and contract address + +./eth-ipfs-state-validator validateTrie --config={path to db config} --type=storage --storage-root={state root hex string} --address={contract address hex string} +"`, Run: func(cmd *cobra.Command, args []string) { subCommand = cmd.CalledAs() logWithCommand = *logrus.WithField("SubCommand", subCommand) @@ -44,15 +61,45 @@ func validateTrie() { logWithCommand.Fatal(err) } v := validator.NewValidator(db) - rootHash := common.HexToHash(rootStr) - if _, err = v.ValidateTrie(rootHash); err != nil { - fmt.Printf("State trie is not complete\r\nerr: %v", err) - logWithCommand.Fatal(err) + switch strings.ToLower(validationType) { + case "f", "full": + if stateRootStr == "" { + logWithCommand.Fatal("must provide a state root for full state validation") + } + stateRoot := common.HexToHash(stateRootStr) + if err = v.ValidateTrie(stateRoot); err != nil { + logWithCommand.Fatalf("State for root %s is not complete\r\nerr: %v", stateRoot.String(), err) + } + logWithCommand.Infof("State for root %s is complete", stateRoot.String()) + case "state": + if stateRootStr == "" { + logWithCommand.Fatal("must provide a state root for state trie validation") + } + stateRoot := common.HexToHash(stateRootStr) + if err = v.ValidateStateTrie(stateRoot); err != nil { + logWithCommand.Fatalf("State trie for root %s is not complete\r\nerr: %v", stateRoot.String(), err) + } + logWithCommand.Infof("State trie for root %s is complete", stateRoot.String()) + case "storage": + if storageRootStr == "" { + logWithCommand.Fatal("must provide a storage root for storage trie validation") + } + if contractAddrStr == "" { + logWithCommand.Fatal("must provide a contract address for storage trie validation") + } + storageRoot := common.HexToHash(storageRootStr) + addr := common.HexToAddress(contractAddrStr) + if err = v.ValidateStorageTrie(addr, storageRoot); err != nil { + logWithCommand.Fatalf("Storage trie for contract %s and root %s not complete\r\nerr: %v", addr.String(), storageRoot.String(), err) + } + logWithCommand.Infof("Storage trie for contract %s and root %s is complete", addr.String(), storageRoot.String()) } - fmt.Printf("State trie for root %s is complete", rootStr) } func init() { rootCmd.AddCommand(validateTrieCmd) - validateTrieCmd.Flags().StringVarP(&rootStr, "root", "r", "", "Root of the state trie we wish to validate") + validateTrieCmd.Flags().StringVarP(&stateRootStr, "state-root", "s", "", "Root of the state trie we wish to validate; for full or state validation") + validateTrieCmd.Flags().StringVarP(&validationType, "type", "t", "full", "Type of validations: full, state, storage") + validateTrieCmd.Flags().StringVarP(&storageRootStr, "storage-root", "o", "", "Root of the storage trie we wish to validate; for storage validation") + validateTrieCmd.Flags().StringVarP(&contractAddrStr, "address", "a", "", "Contract address for the storage trie we wish to validate; for storage validation") } diff --git a/pkg/validator.go b/pkg/validator.go index ce8b445..54c901e 100644 --- a/pkg/validator.go +++ b/pkg/validator.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" "github.com/jmoiron/sqlx" @@ -35,6 +36,8 @@ type Validator struct { } // NewValidator returns a new trie validator +// Validating the completeness of a modified merkle patricia tries requires traversing the entire trie and verifying that +// every node is present, this is an expensive operation func NewValidator(db *sqlx.DB) *Validator { kvs := ipfsethdb.NewKeyValueStore(db) database := ipfsethdb.NewDatabase(db) @@ -45,24 +48,54 @@ func NewValidator(db *sqlx.DB) *Validator { } } -// ValidateTrie returns whether or not the trie for the provided root hash is valid and complete -// Validating the completeness of a modified merkle patricia trie requires traversing the entire trie and verifying that -// every node is present, this is an expensive operation -func (v *Validator) ValidateTrie(root common.Hash) (bool, error) { +// ValidateTrie returns an error if the state and storage tries for the provided state root cannot be confirmed as complete +// This does consider child storage tries +func (v *Validator) ValidateTrie(stateRoot common.Hash) error { // Generate the state.NodeIterator for this root - snapshotTree := snapshot.New(v.kvs, v.trieDB, 0, root, false) + snapshotTree := snapshot.New(v.kvs, v.trieDB, 0, stateRoot, false) stateDB, err := state.New(common.Hash{}, v.stateDatabase, snapshotTree) if err != nil { - return false, err + return err } it := state.NewNodeIterator(stateDB) for it.Next() { - // iterate through entire trie - // it.Next() will return false when we have either completed iteration of the entire trie or have ran into an error + // iterate through entire state trie and descendent storage tries + // it.Next() will return false when we have either completed iteration of the entire trie or have ran into an error (e.g. a missing node) // if we are able to iterate through the entire trie without error then the trie is complete } - if it.Error != nil { - return false, it.Error - } - return true, nil + return it.Error +} + +// ValidateStateTrie returns an error if the state trie for the provided state root cannot be confirmed as complete +// This does not consider child storage tries +func (v *Validator) ValidateStateTrie(stateRoot common.Hash) error { + // Generate the trie.NodeIterator for this root + t, err := v.stateDatabase.OpenTrie(stateRoot) + if err != nil { + return err + } + it := t.NodeIterator(nil) + for it.Next(true) { + // iterate through entire state trie + // it.Next() will return false when we have either completed iteration of the entire trie or have ran into an error (e.g. a missing node) + // if we are able to iterate through the entire trie without error then the trie is complete + } + return it.Error() +} + +// ValidateStorageTrie returns an error if the storage trie for the provided storage root and contract address cannot be confirmed as complete +func (v *Validator) ValidateStorageTrie(address common.Address, storageRoot common.Hash) error { + // Generate the state.NodeIterator for this root + addrHash := crypto.Keccak256Hash(address.Bytes()) + t, err := v.stateDatabase.OpenStorageTrie(addrHash, storageRoot) + if err != nil { + return err + } + it := t.NodeIterator(nil) + for it.Next(true) { + // iterate through entire storage trie + // it.Next() will return false when we have either completed iteration of the entire trie or have ran into an error (e.g. a missing node) + // if we are able to iterate through the entire trie without error then the trie is complete + } + return it.Error() }