From d33949e32f1b8552695f76b3c3ffcbb08665edf7 Mon Sep 17 00:00:00 2001 From: Roy Crihfield Date: Fri, 4 Sep 2020 11:50:49 -0500 Subject: [PATCH] rip statediff from geth fork --- cmd/serve.go | 3 +- pkg/api.go | 12 +- pkg/builder.go | 734 +++++++++ pkg/builder_test.go | 2252 +++++++++++++++++++++++++++ pkg/helpers.go | 96 ++ pkg/service.go | 35 +- pkg/state_types.go | 47 + pkg/testhelpers/helpers.go | 124 ++ pkg/testhelpers/mocks/blockchain.go | 128 ++ pkg/testhelpers/mocks/builder.go | 57 + pkg/testhelpers/test_data.go | 71 + pkg/types.go | 86 + 12 files changed, 3621 insertions(+), 24 deletions(-) create mode 100644 pkg/builder.go create mode 100644 pkg/builder_test.go create mode 100644 pkg/helpers.go create mode 100644 pkg/state_types.go create mode 100644 pkg/testhelpers/helpers.go create mode 100644 pkg/testhelpers/mocks/blockchain.go create mode 100644 pkg/testhelpers/mocks/builder.go create mode 100644 pkg/testhelpers/test_data.go create mode 100644 pkg/types.go diff --git a/cmd/serve.go b/cmd/serve.go index 30c244c..227aefe 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/statediff" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -124,7 +123,7 @@ func startServers(serv sd.IService) error { } if httpPath != "" { logWithCommand.Info("starting up HTTP server") - if _, _, err := rpc.StartHTTPEndpoint(httpPath, serv.APIs(), []string{statediff.APIName}, nil, nil, rpc.HTTPTimeouts{}); err != nil { + if _, _, err := rpc.StartHTTPEndpoint(httpPath, serv.APIs(), []string{sd.APIName}, nil, nil, rpc.HTTPTimeouts{}); err != nil { return err } } diff --git a/pkg/api.go b/pkg/api.go index 57d8c1d..8a158dd 100644 --- a/pkg/api.go +++ b/pkg/api.go @@ -17,10 +17,14 @@ package statediff import ( "context" - - "github.com/ethereum/go-ethereum/statediff" ) +// APIName is the namespace used for the state diffing service API +const APIName = "statediff" + +// APIVersion is the version of the state diffing service API +const APIVersion = "0.0.1" + // PublicStateDiffAPI provides an RPC interface // that can be used to fetch historical diffs from leveldb directly type PublicStateDiffAPI struct { @@ -35,11 +39,11 @@ func NewPublicStateDiffAPI(sds IService) *PublicStateDiffAPI { } // StateDiffAt returns a state diff payload at the specific blockheight -func (api *PublicStateDiffAPI) StateDiffAt(ctx context.Context, blockNumber uint64, params statediff.Params) (*statediff.Payload, error) { +func (api *PublicStateDiffAPI) StateDiffAt(ctx context.Context, blockNumber uint64, params Params) (*Payload, error) { return api.sds.StateDiffAt(blockNumber, params) } // StateTrieAt returns a state trie payload at the specific blockheight -func (api *PublicStateDiffAPI) StateTrieAt(ctx context.Context, blockNumber uint64, params statediff.Params) (*statediff.Payload, error) { +func (api *PublicStateDiffAPI) StateTrieAt(ctx context.Context, blockNumber uint64, params Params) (*Payload, error) { return api.sds.StateTrieAt(blockNumber, params) } diff --git a/pkg/builder.go b/pkg/builder.go new file mode 100644 index 0000000..27e034b --- /dev/null +++ b/pkg/builder.go @@ -0,0 +1,734 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains a batch of utility type declarations used by the tests. As the node +// operates on unique types, a lot of them are needed to check various features. + +package statediff + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + nullHashBytes = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") + emptyNode, _ = rlp.EncodeToBytes([]byte{}) + emptyContractRoot = crypto.Keccak256Hash(emptyNode) +) + +// Builder interface exposes the method for building a state diff between two blocks +type Builder interface { + BuildStateDiffObject(args Args, params Params) (StateObject, error) + BuildStateTrieObject(current *types.Block) (StateObject, error) +} + +type builder struct { + stateCache state.Database +} + +// NewBuilder is used to create a statediff builder +func NewBuilder(stateCache state.Database) Builder { + return &builder{ + stateCache: stateCache, // state cache is safe for concurrent reads + } +} + +// BuildStateTrieObject builds a state trie object from the provided block +func (sdb *builder) BuildStateTrieObject(current *types.Block) (StateObject, error) { + currentTrie, err := sdb.stateCache.OpenTrie(current.Root()) + if err != nil { + return StateObject{}, fmt.Errorf("error creating trie for block %d: %v", current.Number(), err) + } + it := currentTrie.NodeIterator([]byte{}) + stateNodes, err := sdb.buildStateTrie(it) + if err != nil { + return StateObject{}, fmt.Errorf("error collecting state nodes for block %d: %v", current.Number(), err) + } + return StateObject{ + BlockNumber: current.Number(), + BlockHash: current.Hash(), + Nodes: stateNodes, + }, nil +} + +func (sdb *builder) buildStateTrie(it trie.NodeIterator) ([]StateNode, error) { + stateNodes := make([]StateNode, 0) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, err + } + switch ty { + case Leaf: + var account state.Account + if err := rlp.DecodeBytes(nodeElements[1].([]byte), &account); err != nil { + return nil, fmt.Errorf("error decoding account for leaf node at path %x nerror: %v", nodePath, err) + } + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + storageNodes, err := sdb.buildStorageNodesEventual(account.Root, nil, true) + if err != nil { + return nil, fmt.Errorf("failed building eventual storage diffs for account %+v\r\nerror: %v", account, err) + } + stateNodes = append(stateNodes, StateNode{ + NodeType: ty, + Path: nodePath, + LeafKey: leafKey, + NodeValue: node, + StorageNodes: storageNodes, + }) + case Extension, Branch: + stateNodes = append(stateNodes, StateNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + }) + default: + return nil, fmt.Errorf("unexpected node type %s", ty) + } + } + return stateNodes, it.Error() +} + +// BuildStateDiffObject builds a statediff object from two blocks and the provided parameters +func (sdb *builder) BuildStateDiffObject(args Args, params Params) (StateObject, error) { + if !params.IntermediateStateNodes || len(params.WatchedAddresses) > 0 { // if we are watching only specific accounts then we are only diffing leaf nodes + return sdb.buildStateDiffWithoutIntermediateStateNodes(args, params) + } + return sdb.buildStateDiffWithIntermediateStateNodes(args, params) +} + +func (sdb *builder) buildStateDiffWithIntermediateStateNodes(args Args, params Params) (StateObject, error) { + // Load tries for old and new states + oldTrie, err := sdb.stateCache.OpenTrie(args.OldStateRoot) + if err != nil { + return StateObject{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) + } + newTrie, err := sdb.stateCache.OpenTrie(args.NewStateRoot) + if err != nil { + return StateObject{}, fmt.Errorf("error creating trie for newStateRoot: %v", err) + } + + MakeIterator := func(t state.Trie) trie.NodeIterator { return t.NodeIterator([]byte{}) } + // collect a slice of all the intermediate nodes that were touched and exist at B + // a map of their leafkey to all the accounts that were touched and exist at B + // and a slice of all the paths for the nodes in both of the above sets + createdOrUpdatedIntermediateNodes, diffAccountsAtB, diffPathsAtB, err := sdb.createdAndUpdatedStateWithIntermediateNodes(MakeIterator(oldTrie), MakeIterator(newTrie)) + if err != nil { + return StateObject{}, fmt.Errorf("error collecting createdAndUpdatedNodes: %v", err) + } + + // collect a slice of all the nodes that existed at a path in A that doesn't exist in B + // a map of their leafkey to all the accounts that were touched and exist at A + emptiedPaths, diffAccountsAtA, err := sdb.deletedOrUpdatedState(MakeIterator(oldTrie), MakeIterator(newTrie), diffPathsAtB) + if err != nil { + return StateObject{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) + } + + // collect and sort the leafkey keys for both account mappings into a slice + createKeys := sortKeys(diffAccountsAtB) + deleteKeys := sortKeys(diffAccountsAtA) + + // and then find the intersection of these keys + // these are the leafkeys for the accounts which exist at both A and B but are different + // this also mutates the passed in createKeys and deleteKeys, removing the intersection keys + // and leaving the truly created or deleted keys in place + updatedKeys := findIntersection(createKeys, deleteKeys) + + // build the diff nodes for the updated accounts using the mappings at both A and B as directed by the keys found as the intersection of the two + updatedAccounts, err := sdb.buildAccountUpdates(diffAccountsAtB, diffAccountsAtA, updatedKeys, params.WatchedStorageSlots, params.IntermediateStorageNodes) + if err != nil { + return StateObject{}, fmt.Errorf("error building diff for updated accounts: %v", err) + } + // build the diff nodes for created accounts + createdAccounts, err := sdb.buildAccountCreations(diffAccountsAtB, params.WatchedStorageSlots, params.IntermediateStorageNodes) + if err != nil { + return StateObject{}, fmt.Errorf("error building diff for created accounts: %v", err) + } + + // assemble all of the nodes into the statediff object, including the intermediate nodes + return StateObject{ + BlockNumber: args.BlockNumber, + BlockHash: args.BlockHash, + Nodes: append(append(append(updatedAccounts, createdAccounts...), createdOrUpdatedIntermediateNodes...), emptiedPaths...), + }, nil +} + +func (sdb *builder) buildStateDiffWithoutIntermediateStateNodes(args Args, params Params) (StateObject, error) { + // Load tries for old (A) and new (B) states + oldTrie, err := sdb.stateCache.OpenTrie(args.OldStateRoot) + if err != nil { + return StateObject{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) + } + newTrie, err := sdb.stateCache.OpenTrie(args.NewStateRoot) + if err != nil { + return StateObject{}, fmt.Errorf("error creating trie for newStateRoot: %v", err) + } + + // collect a map of their leafkey to all the accounts that were touched and exist at B + // and a slice of all the paths for the nodes in both of the above sets + diffAccountsAtB, diffPathsAtB, err := sdb.createdAndUpdatedState(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), params.WatchedAddresses) + if err != nil { + return StateObject{}, fmt.Errorf("error collecting createdAndUpdatedNodes: %v", err) + } + + // collect a slice of all the nodes that existed at a path in A that doesn't exist in B + // a map of their leafkey to all the accounts that were touched and exist at A + emptiedPaths, diffAccountsAtA, err := sdb.deletedOrUpdatedState(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), diffPathsAtB) + if err != nil { + return StateObject{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) + } + + // collect and sort the leafkeys for both account mappings into a slice + createKeys := sortKeys(diffAccountsAtB) + deleteKeys := sortKeys(diffAccountsAtA) + + // and then find the intersection of these keys + // these are the leafkeys for the accounts which exist at both A and B but are different + // this also mutates the passed in createKeys and deleteKeys, removing in intersection keys + // and leaving the truly created or deleted keys in place + updatedKeys := findIntersection(createKeys, deleteKeys) + + // build the diff nodes for the updated accounts using the mappings at both A and B as directed by the keys found as the intersection of the two + updatedAccounts, err := sdb.buildAccountUpdates(diffAccountsAtB, diffAccountsAtA, updatedKeys, params.WatchedStorageSlots, params.IntermediateStorageNodes) + if err != nil { + return StateObject{}, fmt.Errorf("error building diff for updated accounts: %v", err) + } + // build the diff nodes for created accounts + createdAccounts, err := sdb.buildAccountCreations(diffAccountsAtB, params.WatchedStorageSlots, params.IntermediateStorageNodes) + if err != nil { + return StateObject{}, fmt.Errorf("error building diff for created accounts: %v", err) + } + + // assemble all of the nodes into the statediff object + return StateObject{ + BlockNumber: args.BlockNumber, + BlockHash: args.BlockHash, + Nodes: append(append(updatedAccounts, createdAccounts...), emptiedPaths...), + }, nil +} + +// createdAndUpdatedState returns +// a mapping of their leafkeys to all the accounts that exist in a different state at B than A +// and a slice of the paths for all of the nodes included in both +func (sdb *builder) createdAndUpdatedState(a, b trie.NodeIterator, watchedAddresses []common.Address) (AccountMap, map[string]bool, error) { + diffPathsAtB := make(map[string]bool) + diffAcountsAtB := make(AccountMap) + it, _ := trie.NewDifferenceIterator(a, b) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, nil, err + } + if ty == Leaf { + // created vs updated is important for leaf nodes since we need to diff their storage + // so we need to map all changed accounts at B to their leafkey, since account can change pathes but not leafkey + var account state.Account + if err := rlp.DecodeBytes(nodeElements[1].([]byte), &account); err != nil { + return nil, nil, fmt.Errorf("error decoding account for leaf node at path %x nerror: %v", nodePath, err) + } + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + if isWatchedAddress(watchedAddresses, leafKey) { + diffAcountsAtB[common.Bytes2Hex(leafKey)] = accountWrapper{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + LeafKey: leafKey, + Account: &account, + } + } + } + // add both intermediate and leaf node paths to the list of diffPathsAtB + diffPathsAtB[common.Bytes2Hex(nodePath)] = true + } + return diffAcountsAtB, diffPathsAtB, it.Error() +} + +// createdAndUpdatedStateWithIntermediateNodes returns +// a slice of all the intermediate nodes that exist in a different state at B than A +// a mapping of their leafkeys to all the accounts that exist in a different state at B than A +// and a slice of the paths for all of the nodes included in both +func (sdb *builder) createdAndUpdatedStateWithIntermediateNodes(a, b trie.NodeIterator) ([]StateNode, AccountMap, map[string]bool, error) { + createdOrUpdatedIntermediateNodes := make([]StateNode, 0) + diffPathsAtB := make(map[string]bool) + diffAcountsAtB := make(AccountMap) + it, _ := trie.NewDifferenceIterator(a, b) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, nil, nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, nil, nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, nil, nil, err + } + switch ty { + case Leaf: + // created vs updated is important for leaf nodes since we need to diff their storage + // so we need to map all changed accounts at B to their leafkey, since account can change pathes but not leafkey + var account state.Account + if err := rlp.DecodeBytes(nodeElements[1].([]byte), &account); err != nil { + return nil, nil, nil, fmt.Errorf("error decoding account for leaf node at path %x nerror: %v", nodePath, err) + } + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + diffAcountsAtB[common.Bytes2Hex(leafKey)] = accountWrapper{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + LeafKey: leafKey, + Account: &account, + } + case Extension, Branch: + // create a diff for any intermediate node that has changed at b + // created vs updated makes no difference for intermediate nodes since we do not need to diff storage + createdOrUpdatedIntermediateNodes = append(createdOrUpdatedIntermediateNodes, StateNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + }) + default: + return nil, nil, nil, fmt.Errorf("unexpected node type %s", ty) + } + // add both intermediate and leaf node paths to the list of diffPathsAtB + diffPathsAtB[common.Bytes2Hex(nodePath)] = true + } + return createdOrUpdatedIntermediateNodes, diffAcountsAtB, diffPathsAtB, it.Error() +} + +// deletedOrUpdatedState returns a slice of all the pathes that are emptied at B +// and a mapping of their leafkeys to all the accounts that exist in a different state at A than B +func (sdb *builder) deletedOrUpdatedState(a, b trie.NodeIterator, diffPathsAtB map[string]bool) ([]StateNode, AccountMap, error) { + emptiedPaths := make([]StateNode, 0) + diffAccountAtA := make(AccountMap) + it, _ := trie.NewDifferenceIterator(b, a) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + // if this nodePath did not show up in diffPathsAtB + // that means the node at this path was deleted (or moved) in B + // emit an empty "removed" diff to signify as such + if _, ok := diffPathsAtB[common.Bytes2Hex(nodePath)]; !ok { + emptiedPaths = append(emptiedPaths, StateNode{ + Path: nodePath, + NodeValue: []byte{}, + NodeType: Removed, + }) + } + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, nil, err + } + switch ty { + case Leaf: + // map all different accounts at A to their leafkey + var account state.Account + if err := rlp.DecodeBytes(nodeElements[1].([]byte), &account); err != nil { + return nil, nil, fmt.Errorf("error decoding account for leaf node at path %x nerror: %v", nodePath, err) + } + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + diffAccountAtA[common.Bytes2Hex(leafKey)] = accountWrapper{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + LeafKey: leafKey, + Account: &account, + } + case Extension, Branch: + // fall through, we did everything we need to do with these node types + default: + return nil, nil, fmt.Errorf("unexpected node type %s", ty) + } + } + return emptiedPaths, diffAccountAtA, it.Error() +} + +// buildAccountUpdates uses the account diffs maps for A => B and B => A and the known intersection of their leafkeys +// to generate the statediff node objects for all of the accounts that existed at both A and B but in different states +// needs to be called before building account creations and deletions as this mutates +// those account maps to remove the accounts which were updated +func (sdb *builder) buildAccountUpdates(creations, deletions AccountMap, updatedKeys []string, watchedStorageKeys []common.Hash, intermediateStorageNodes bool) ([]StateNode, error) { + updatedAccounts := make([]StateNode, 0, len(updatedKeys)) + var err error + for _, key := range updatedKeys { + createdAcc := creations[key] + deletedAcc := deletions[key] + var storageDiffs []StorageNode + if deletedAcc.Account != nil && createdAcc.Account != nil { + oldSR := deletedAcc.Account.Root + newSR := createdAcc.Account.Root + storageDiffs, err = sdb.buildStorageNodesIncremental(oldSR, newSR, watchedStorageKeys, intermediateStorageNodes) + if err != nil { + return nil, fmt.Errorf("failed building incremental storage diffs for account with leafkey %s\r\nerror: %v", key, err) + } + } + updatedAccounts = append(updatedAccounts, StateNode{ + NodeType: createdAcc.NodeType, + Path: createdAcc.Path, + NodeValue: createdAcc.NodeValue, + LeafKey: createdAcc.LeafKey, + StorageNodes: storageDiffs, + }) + delete(creations, key) + delete(deletions, key) + } + + return updatedAccounts, nil +} + +// buildAccountCreations returns the statediff node objects for all the accounts that exist at B but not at A +func (sdb *builder) buildAccountCreations(accounts AccountMap, watchedStorageKeys []common.Hash, intermediateStorageNodes bool) ([]StateNode, error) { + accountDiffs := make([]StateNode, 0, len(accounts)) + for _, val := range accounts { + // For account creations, any storage node contained is a diff + storageDiffs, err := sdb.buildStorageNodesEventual(val.Account.Root, watchedStorageKeys, intermediateStorageNodes) + if err != nil { + return nil, fmt.Errorf("failed building eventual storage diffs for node %x\r\nerror: %v", val.Path, err) + } + accountDiffs = append(accountDiffs, StateNode{ + NodeType: val.NodeType, + Path: val.Path, + LeafKey: val.LeafKey, + NodeValue: val.NodeValue, + StorageNodes: storageDiffs, + }) + } + + return accountDiffs, nil +} + +// buildStorageNodesEventual builds the storage diff node objects for a created account +// i.e. it returns all the storage nodes at this state, since there is no previous state +func (sdb *builder) buildStorageNodesEventual(sr common.Hash, watchedStorageKeys []common.Hash, intermediateNodes bool) ([]StorageNode, error) { + if bytes.Equal(sr.Bytes(), emptyContractRoot.Bytes()) { + return nil, nil + } + log.Debug("Storage Root For Eventual Diff", "root", sr.Hex()) + sTrie, err := sdb.stateCache.OpenTrie(sr) + if err != nil { + log.Info("error in build storage diff eventual", "error", err) + return nil, err + } + it := sTrie.NodeIterator(make([]byte, 0)) + return sdb.buildStorageNodesFromTrie(it, watchedStorageKeys, intermediateNodes) +} + +// buildStorageNodesFromTrie returns all the storage diff node objects in the provided node interator +// if any storage keys are provided it will only return those leaf nodes +// including intermediate nodes can be turned on or off +func (sdb *builder) buildStorageNodesFromTrie(it trie.NodeIterator, watchedStorageKeys []common.Hash, intermediateNodes bool) ([]StorageNode, error) { + storageDiffs := make([]StorageNode, 0) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, err + } + switch ty { + case Leaf: + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + if isWatchedStorageKey(watchedStorageKeys, leafKey) { + storageDiffs = append(storageDiffs, StorageNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + LeafKey: leafKey, + }) + } + case Extension, Branch: + if intermediateNodes { + storageDiffs = append(storageDiffs, StorageNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + }) + } + default: + return nil, fmt.Errorf("unexpected node type %s", ty) + } + } + return storageDiffs, it.Error() +} + +// buildStorageNodesIncremental builds the storage diff node objects for all nodes that exist in a different state at B than A +func (sdb *builder) buildStorageNodesIncremental(oldSR common.Hash, newSR common.Hash, watchedStorageKeys []common.Hash, intermediateNodes bool) ([]StorageNode, error) { + if bytes.Equal(newSR.Bytes(), oldSR.Bytes()) { + return nil, nil + } + log.Debug("Storage Roots for Incremental Diff", "old", oldSR.Hex(), "new", newSR.Hex()) + oldTrie, err := sdb.stateCache.OpenTrie(oldSR) + if err != nil { + return nil, err + } + newTrie, err := sdb.stateCache.OpenTrie(newSR) + if err != nil { + return nil, err + } + + createdOrUpdatedStorage, diffPathsAtB, err := sdb.createdAndUpdatedStorage(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), watchedStorageKeys, intermediateNodes) + if err != nil { + return nil, err + } + deletedStorage, err := sdb.deletedOrUpdatedStorage(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), diffPathsAtB, watchedStorageKeys, intermediateNodes) + if err != nil { + return nil, err + } + return append(createdOrUpdatedStorage, deletedStorage...), nil +} + +func (sdb *builder) createdAndUpdatedStorage(a, b trie.NodeIterator, watchedKeys []common.Hash, intermediateNodes bool) ([]StorageNode, map[string]bool, error) { + createdOrUpdatedStorage := make([]StorageNode, 0) + diffPathsAtB := make(map[string]bool) + it, _ := trie.NewDifferenceIterator(a, b) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, nil, err + } + switch ty { + case Leaf: + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + if isWatchedStorageKey(watchedKeys, leafKey) { + createdOrUpdatedStorage = append(createdOrUpdatedStorage, StorageNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + LeafKey: leafKey, + }) + } + case Extension, Branch: + if intermediateNodes { + createdOrUpdatedStorage = append(createdOrUpdatedStorage, StorageNode{ + NodeType: ty, + Path: nodePath, + NodeValue: node, + }) + } + default: + return nil, nil, fmt.Errorf("unexpected node type %s", ty) + } + diffPathsAtB[common.Bytes2Hex(nodePath)] = true + } + return createdOrUpdatedStorage, diffPathsAtB, it.Error() +} + +func (sdb *builder) deletedOrUpdatedStorage(a, b trie.NodeIterator, diffPathsAtB map[string]bool, watchedKeys []common.Hash, intermediateNodes bool) ([]StorageNode, error) { + deletedStorage := make([]StorageNode, 0) + it, _ := trie.NewDifferenceIterator(b, a) + for it.Next(true) { + // skip value nodes + if it.Leaf() { + continue + } + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { + continue + } + nodePath := make([]byte, len(it.Path())) + copy(nodePath, it.Path()) + // if this node path showed up in diffPathsAtB + // that means this node was updated at B and we already have the updated diff for it + // otherwise that means this node was deleted in B and we need to add a "removed" diff to represent that event + if _, ok := diffPathsAtB[common.Bytes2Hex(nodePath)]; ok { + continue + } + node, err := sdb.stateCache.TrieDB().Node(it.Hash()) + if err != nil { + return nil, err + } + var nodeElements []interface{} + if err := rlp.DecodeBytes(node, &nodeElements); err != nil { + return nil, err + } + ty, err := CheckKeyType(nodeElements) + if err != nil { + return nil, err + } + switch ty { + case Leaf: + partialPath := trie.CompactToHex(nodeElements[0].([]byte)) + valueNodePath := append(nodePath, partialPath...) + encodedPath := trie.HexToCompact(valueNodePath) + leafKey := encodedPath[1:] + if isWatchedStorageKey(watchedKeys, leafKey) { + deletedStorage = append(deletedStorage, StorageNode{ + NodeType: Removed, + Path: nodePath, + NodeValue: []byte{}, + }) + } + case Extension, Branch: + if intermediateNodes { + deletedStorage = append(deletedStorage, StorageNode{ + NodeType: Removed, + Path: nodePath, + NodeValue: []byte{}, + }) + } + default: + return nil, fmt.Errorf("unexpected node type %s", ty) + } + } + return deletedStorage, it.Error() +} + +// isWatchedAddress is used to check if a state account corresponds to one of the addresses the builder is configured to watch +func isWatchedAddress(watchedAddresses []common.Address, stateLeafKey []byte) bool { + // If we aren't watching any specific addresses, we are watching everything + if len(watchedAddresses) == 0 { + return true + } + for _, addr := range watchedAddresses { + addrHashKey := crypto.Keccak256(addr.Bytes()) + if bytes.Equal(addrHashKey, stateLeafKey) { + return true + } + } + return false +} + +// isWatchedStorageKey is used to check if a storage leaf corresponds to one of the storage slots the builder is configured to watch +func isWatchedStorageKey(watchedKeys []common.Hash, storageLeafKey []byte) bool { + // If we aren't watching any specific addresses, we are watching everything + if len(watchedKeys) == 0 { + return true + } + for _, hashKey := range watchedKeys { + if bytes.Equal(hashKey.Bytes(), storageLeafKey) { + return true + } + } + return false +} diff --git a/pkg/builder_test.go b/pkg/builder_test.go new file mode 100644 index 0000000..b5ddeb9 --- /dev/null +++ b/pkg/builder_test.go @@ -0,0 +1,2252 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package statediff_test + +import ( + "bytes" + "math/big" + "sort" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + + statediff "github.com/vulcanize/eth-statediff-service/pkg" + "github.com/vulcanize/eth-statediff-service/pkg/testhelpers" +) + +// TODO: add test that filters on address +var ( + contractLeafKey []byte + emptyDiffs = make([]statediff.StateNode, 0) + emptyStorage = make([]statediff.StorageNode, 0) + block0, block1, block2, block3, block4, block5, block6 *types.Block + builder statediff.Builder + miningReward = int64(2000000000000000000) + minerAddress = common.HexToAddress("0x0") + minerLeafKey = testhelpers.AddressToLeafKey(minerAddress) + + balanceChange10000 = int64(10000) + balanceChange1000 = int64(1000) + block1BankBalance = int64(99990000) + block1Account1Balance = int64(10000) + block2Account2Balance = int64(1000) + + slot0 = common.HexToHash("0") + slot1 = common.HexToHash("1") + slot2 = common.HexToHash("2") + slot3 = common.HexToHash("3") + + slot0StorageKey = crypto.Keccak256Hash(slot0[:]) + slot1StorageKey = crypto.Keccak256Hash(slot1[:]) + slot2StorageKey = crypto.Keccak256Hash(slot2[:]) + slot3StorageKey = crypto.Keccak256Hash(slot3[:]) + + slot0StorageValue = common.Hex2Bytes("94703c4b2bd70c169f5717101caee543299fc946c7") // prefixed AccountAddr1 + slot1StorageValue = common.Hex2Bytes("01") + slot2StorageValue = common.Hex2Bytes("09") + slot3StorageValue = common.Hex2Bytes("03") + + slot0StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), + slot0StorageValue, + }) + slot1StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), + slot1StorageValue, + }) + slot2StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("305787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace"), + slot2StorageValue, + }) + slot3StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("32575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b"), + slot3StorageValue, + }) + slot0StorageLeafRootNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("20290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), + slot0StorageValue, + }) + + contractAccountAtBlock2, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(0), + CodeHash: common.HexToHash("0xaaea5efba4fd7b45d7ec03918ac5d8b31aa93b48986af0e6b591f0f087c80127").Bytes(), + Root: crypto.Keccak256Hash(block2StorageBranchRootNode), + }) + contractAccountAtBlock2LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45"), + contractAccountAtBlock2, + }) + contractAccountAtBlock3, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(0), + CodeHash: common.HexToHash("0xaaea5efba4fd7b45d7ec03918ac5d8b31aa93b48986af0e6b591f0f087c80127").Bytes(), + Root: crypto.Keccak256Hash(block3StorageBranchRootNode), + }) + contractAccountAtBlock3LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45"), + contractAccountAtBlock3, + }) + contractAccountAtBlock4, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(0), + CodeHash: common.HexToHash("0xaaea5efba4fd7b45d7ec03918ac5d8b31aa93b48986af0e6b591f0f087c80127").Bytes(), + Root: crypto.Keccak256Hash(block4StorageBranchRootNode), + }) + contractAccountAtBlock4LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45"), + contractAccountAtBlock4, + }) + contractAccountAtBlock5, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(0), + CodeHash: common.HexToHash("0xaaea5efba4fd7b45d7ec03918ac5d8b31aa93b48986af0e6b591f0f087c80127").Bytes(), + Root: crypto.Keccak256Hash(slot0StorageLeafRootNode), + }) + contractAccountAtBlock5LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45"), + contractAccountAtBlock5, + }) + + minerAccountAtBlock1, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + minerAccountAtBlock1LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"), + minerAccountAtBlock1, + }) + minerAccountAtBlock2, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(miningReward + miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + minerAccountAtBlock2LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"), + minerAccountAtBlock2, + }) + + account1AtBlock1, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(balanceChange10000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account1AtBlock1LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3926db69aaced518e9b9f0f434a473e7174109c943548bb8f23be41ca76d9ad2"), + account1AtBlock1, + }) + account1AtBlock2, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 2, + Balance: big.NewInt(block1Account1Balance - balanceChange1000 + balanceChange1000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account1AtBlock2LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3926db69aaced518e9b9f0f434a473e7174109c943548bb8f23be41ca76d9ad2"), + account1AtBlock2, + }) + account1AtBlock5, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 2, + Balance: big.NewInt(block1Account1Balance - balanceChange1000 + balanceChange1000 + miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account1AtBlock5LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3926db69aaced518e9b9f0f434a473e7174109c943548bb8f23be41ca76d9ad2"), + account1AtBlock5, + }) + account1AtBlock6, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 3, + Balance: big.NewInt(block1Account1Balance - balanceChange1000 + balanceChange1000 + miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account1AtBlock6LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3926db69aaced518e9b9f0f434a473e7174109c943548bb8f23be41ca76d9ad2"), + account1AtBlock6, + }) + + account2AtBlock2, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(balanceChange1000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account2AtBlock2LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3957f3e2f04a0764c3a0491b175f69926da61efbcc8f61fa1455fd2d2b4cdd45"), + account2AtBlock2, + }) + account2AtBlock3, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(block2Account2Balance + miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account2AtBlock3LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3957f3e2f04a0764c3a0491b175f69926da61efbcc8f61fa1455fd2d2b4cdd45"), + account2AtBlock3, + }) + account2AtBlock4, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(block2Account2Balance + miningReward*2), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account2AtBlock4LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3957f3e2f04a0764c3a0491b175f69926da61efbcc8f61fa1455fd2d2b4cdd45"), + account2AtBlock4, + }) + account2AtBlock6, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(block2Account2Balance + miningReward*3), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + account2AtBlock6LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3957f3e2f04a0764c3a0491b175f69926da61efbcc8f61fa1455fd2d2b4cdd45"), + account2AtBlock6, + }) + + bankAccountAtBlock0, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 0, + Balance: big.NewInt(testhelpers.TestBankFunds.Int64()), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock0LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("2000bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock0, + }) + bankAccountAtBlock1, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(testhelpers.TestBankFunds.Int64() - balanceChange10000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock1LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock1, + }) + bankAccountAtBlock2, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 2, + Balance: big.NewInt(block1BankBalance - balanceChange1000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock2LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock2, + }) + bankAccountAtBlock3, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 3, + Balance: big.NewInt(99989000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock3LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock3, + }) + bankAccountAtBlock4, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 6, + Balance: big.NewInt(99989000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock4LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock4, + }) + bankAccountAtBlock5, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 7, + Balance: big.NewInt(99989000), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock5LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock5, + }) + + block1BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock1LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock1LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account1AtBlock1LeafNode), + []byte{}, + []byte{}, + }) + block2BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock2LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock2LeafNode), + crypto.Keccak256(contractAccountAtBlock2LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account2AtBlock2LeafNode), + []byte{}, + crypto.Keccak256(account1AtBlock2LeafNode), + []byte{}, + []byte{}, + }) + block3BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock3LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock2LeafNode), + crypto.Keccak256(contractAccountAtBlock3LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account2AtBlock3LeafNode), + []byte{}, + crypto.Keccak256(account1AtBlock2LeafNode), + []byte{}, + []byte{}, + }) + block4BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock4LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock2LeafNode), + crypto.Keccak256(contractAccountAtBlock4LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account2AtBlock4LeafNode), + []byte{}, + crypto.Keccak256(account1AtBlock2LeafNode), + []byte{}, + []byte{}, + }) + block5BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock5LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock2LeafNode), + crypto.Keccak256(contractAccountAtBlock5LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account2AtBlock4LeafNode), + []byte{}, + crypto.Keccak256(account1AtBlock5LeafNode), + []byte{}, + []byte{}, + }) + block6BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256(bankAccountAtBlock5LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(minerAccountAtBlock2LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(account2AtBlock6LeafNode), + []byte{}, + crypto.Keccak256(account1AtBlock6LeafNode), + []byte{}, + []byte{}, + }) + + block2StorageBranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + []byte{}, + []byte{}, + crypto.Keccak256(slot0StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(slot1StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + }) + block3StorageBranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + []byte{}, + []byte{}, + crypto.Keccak256(slot0StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(slot1StorageLeafNode), + crypto.Keccak256(slot3StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + }) + block4StorageBranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + []byte{}, + []byte{}, + crypto.Keccak256(slot0StorageLeafNode), + []byte{}, + crypto.Keccak256(slot2StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + }) +) + +func TestBuilder(t *testing.T) { + blocks, chain := testhelpers.MakeChain(3, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + block3 = blocks[2] + params := statediff.Params{} + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testEmptyDiff", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock0", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: testhelpers.NullHash, + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock0LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock1", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock2", + // 1000 transferred from testBankAddress to account1Addr + // 1000 transferred from account1Addr to account2Addr + // account1addr creates a new contract + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock2LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot0StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock3", + //the contract's storage is changed + //and the block is mined by account 2 + statediff.Args{ + OldStateRoot: block2.Root(), + NewStateRoot: block3.Root(), + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock3LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: slot3StorageKey.Bytes(), + NodeValue: slot3StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuilderWithIntermediateNodes(t *testing.T) { + blocks, chain := testhelpers.MakeChain(3, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + block3 = blocks[2] + blocks = append([]*types.Block{block0}, blocks...) + params := statediff.Params{ + IntermediateStateNodes: true, + IntermediateStorageNodes: true, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testEmptyDiff", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock0", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: testhelpers.NullHash, + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock0LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock1", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block1BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock2", + // 1000 transferred from testBankAddress to account1Addr + // 1000 transferred from account1Addr to account2Addr + // account1addr creates a new contract + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block2BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock2LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block2StorageBranchRootNode, + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot0StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock3", + //the contract's storage is changed + //and the block is mined by account 2 + statediff.Args{ + OldStateRoot: block2.Root(), + NewStateRoot: block3.Root(), + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block3BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock3LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block3StorageBranchRootNode, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: slot3StorageKey.Bytes(), + NodeValue: slot3StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for i, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\r\n\r\n\r\nexpected state diff: %+v", diff, test.expected) + } + // Let's also confirm that our root state nodes form the state root hash in the headers + if i > 0 { + block := blocks[i-1] + expectedStateRoot := block.Root() + for _, node := range test.expected.Nodes { + if bytes.Equal(node.Path, []byte{}) { + stateRoot := crypto.Keccak256Hash(node.NodeValue) + if !bytes.Equal(expectedStateRoot.Bytes(), stateRoot.Bytes()) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual stateroot: %x\r\nexpected stateroot: %x", stateRoot.Bytes(), expectedStateRoot.Bytes()) + } + } + } + } + } +} + +func TestBuilderWithWatchedAddressList(t *testing.T) { + blocks, chain := testhelpers.MakeChain(3, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + block3 = blocks[2] + params := statediff.Params{ + WatchedAddresses: []common.Address{testhelpers.Account1Addr, testhelpers.ContractAddr}, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testEmptyDiff", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock0", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: testhelpers.NullHash, + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock1", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock2", + //1000 transferred from testBankAddress to account1Addr + //1000 transferred from account1Addr to account2Addr + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock2LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot0StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock3", + //the contract's storage is changed + //and the block is mined by account 2 + statediff.Args{ + OldStateRoot: block2.Root(), + NewStateRoot: block3.Root(), + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock3LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: slot3StorageKey.Bytes(), + NodeValue: slot3StorageLeafNode, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { + blocks, chain := testhelpers.MakeChain(3, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + block3 = blocks[2] + params := statediff.Params{ + WatchedAddresses: []common.Address{testhelpers.Account1Addr, testhelpers.ContractAddr}, + WatchedStorageSlots: []common.Hash{slot1StorageKey}, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testEmptyDiff", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock0", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: testhelpers.NullHash, + NewStateRoot: block0.Root(), + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block0.Number(), + BlockHash: block0.Hash(), + Nodes: emptyDiffs, + }, + }, + { + "testBlock1", + //10000 transferred from testBankAddress to account1Addr + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock2", + //1000 transferred from testBankAddress to account1Addr + //1000 transferred from account1Addr to account2Addr + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock2LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock3", + //the contract's storage is changed + //and the block is mined by account 2 + statediff.Args{ + OldStateRoot: block2.Root(), + NewStateRoot: block3.Root(), + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { + blocks, chain := testhelpers.MakeChain(6, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block3 = blocks[2] + block4 = blocks[3] + block5 = blocks[4] + block6 = blocks[5] + params := statediff.Params{ + IntermediateStateNodes: true, + IntermediateStorageNodes: true, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + // blocks 0-3 are the same as in TestBuilderWithIntermediateNodes + { + "testBlock4", + statediff.Args{ + OldStateRoot: block3.Root(), + NewStateRoot: block4.Root(), + BlockNumber: block4.Number(), + BlockHash: block4.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block4.Number(), + BlockHash: block4.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block4BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock4LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock4LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block4StorageBranchRootNode, + }, + { + Path: []byte{'\x04'}, + NodeType: statediff.Leaf, + LeafKey: slot2StorageKey.Bytes(), + NodeValue: slot2StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock4LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock5", + statediff.Args{ + OldStateRoot: block4.Root(), + NewStateRoot: block5.Root(), + BlockNumber: block5.Number(), + BlockHash: block5.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block5.Number(), + BlockHash: block5.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block5BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock5LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock5LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + NodeValue: slot0StorageLeafRootNode, + LeafKey: slot0StorageKey.Bytes(), + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x04'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock5LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock6", + statediff.Args{ + OldStateRoot: block5.Root(), + NewStateRoot: block6.Root(), + BlockNumber: block6.Number(), + BlockHash: block6.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block6.Number(), + BlockHash: block6.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block6BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock6LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock6LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\r\n\r\n\r\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing.T) { + blocks, chain := testhelpers.MakeChain(6, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block3 = blocks[2] + block4 = blocks[3] + block5 = blocks[4] + block6 = blocks[5] + params := statediff.Params{ + IntermediateStateNodes: false, + IntermediateStorageNodes: false, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + // blocks 0-3 are the same as in TestBuilderWithIntermediateNodes + { + "testBlock4", + statediff.Args{ + OldStateRoot: block3.Root(), + NewStateRoot: block4.Root(), + BlockNumber: block4.Number(), + BlockHash: block4.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block4.Number(), + BlockHash: block4.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock4LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock4LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x04'}, + NodeType: statediff.Leaf, + LeafKey: slot2StorageKey.Bytes(), + NodeValue: slot2StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock4LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock5", + statediff.Args{ + OldStateRoot: block4.Root(), + NewStateRoot: block5.Root(), + BlockNumber: block5.Number(), + BlockHash: block5.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block5.Number(), + BlockHash: block5.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock5LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock5LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + NodeValue: slot0StorageLeafRootNode, + LeafKey: slot0StorageKey.Bytes(), + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x04'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock5LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock6", + statediff.Args{ + OldStateRoot: block5.Root(), + NewStateRoot: block6.Root(), + BlockNumber: block6.Number(), + BlockHash: block6.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block6.Number(), + BlockHash: block6.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x06'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock6LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock6LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\r\n\r\n\r\nexpected state diff: %+v", diff, test.expected) + } + } +} + +var ( + slot00StorageValue = common.Hex2Bytes("9471562b71999873db5b286df957af199ec94617f7") // prefixed TestBankAddress + + slot00StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), + slot00StorageValue, + }) + + contractAccountAtBlock01, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(0), + CodeHash: common.HexToHash("0xaaea5efba4fd7b45d7ec03918ac5d8b31aa93b48986af0e6b591f0f087c80127").Bytes(), + Root: crypto.Keccak256Hash(block01StorageBranchRootNode), + }) + contractAccountAtBlock01LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("3cb2583748c26e89ef19c2a8529b05a270f735553b4d44b6f2a1894987a71c8b"), + contractAccountAtBlock01, + }) + + bankAccountAtBlock01, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 1, + Balance: big.NewInt(testhelpers.TestBankFunds.Int64() + miningReward), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock01LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock01, + }) + bankAccountAtBlock02, _ = rlp.EncodeToBytes(state.Account{ + Nonce: 2, + Balance: big.NewInt(testhelpers.TestBankFunds.Int64() + miningReward*2), + CodeHash: testhelpers.NullCodeHash.Bytes(), + Root: testhelpers.EmptyContractRoot, + }) + bankAccountAtBlock02LeafNode, _ = rlp.EncodeToBytes([]interface{}{ + common.Hex2Bytes("2000bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), + bankAccountAtBlock02, + }) + + block01BranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + crypto.Keccak256Hash(bankAccountAtBlock01LeafNode), + crypto.Keccak256Hash(contractAccountAtBlock01LeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + }) + + block01StorageBranchRootNode, _ = rlp.EncodeToBytes([]interface{}{ + []byte{}, + []byte{}, + crypto.Keccak256(slot00StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + crypto.Keccak256(slot1StorageLeafNode), + []byte{}, + []byte{}, + []byte{}, + []byte{}, + []byte{}, + }) +) + +func TestBuilderWithMovedAccount(t *testing.T) { + blocks, chain := testhelpers.MakeChain(2, testhelpers.Genesis, testhelpers.TestSelfDestructChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + params := statediff.Params{ + IntermediateStateNodes: true, + IntermediateStorageNodes: true, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testBlock1", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block01BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock01LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x01'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock01LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block01StorageBranchRootNode, + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot00StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + }, + }, + }, + { + "testBlock2", + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock02LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x01'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\r\n\r\n\r\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuilderWithMovedAccountOnlyLeafs(t *testing.T) { + blocks, chain := testhelpers.MakeChain(2, testhelpers.Genesis, testhelpers.TestSelfDestructChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block0 = testhelpers.Genesis + block1 = blocks[0] + block2 = blocks[1] + params := statediff.Params{ + IntermediateStateNodes: false, + IntermediateStorageNodes: false, + } + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + startingArguments statediff.Args + expected *statediff.StateObject + }{ + { + "testBlock1", + statediff.Args{ + OldStateRoot: block0.Root(), + NewStateRoot: block1.Root(), + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock01LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x01'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock01LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot00StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + }, + }, + }, + { + "testBlock2", + statediff.Args{ + OldStateRoot: block1.Root(), + NewStateRoot: block2.Root(), + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + }, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock02LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x01'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Removed, + NodeValue: []byte{}, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) + if err != nil { + t.Error(err) + } + receivedStateDiffRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] }) + sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] }) + if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state diff: %+v\r\n\r\n\r\nexpected state diff: %+v", diff, test.expected) + } + } +} + +func TestBuildStateTrie(t *testing.T) { + blocks, chain := testhelpers.MakeChain(3, testhelpers.Genesis, testhelpers.TestChainGen) + contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr) + defer chain.Stop() + block1 = blocks[0] + block2 = blocks[1] + block3 = blocks[2] + builder = statediff.NewBuilder(chain.StateCache()) + + var tests = []struct { + name string + block *types.Block + expected *statediff.StateObject + }{ + { + "testBlock1", + block1, + &statediff.StateObject{ + BlockNumber: block1.Number(), + BlockHash: block1.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block1BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock1LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock2", + block2, + &statediff.StateObject{ + BlockNumber: block2.Number(), + BlockHash: block2.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block2BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock2LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block2StorageBranchRootNode, + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot0StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + { + "testBlock3", + block3, + &statediff.StateObject{ + BlockNumber: block3.Number(), + BlockHash: block3.Hash(), + Nodes: []statediff.StateNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block3BranchRootNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x00'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.BankLeafKey, + NodeValue: bankAccountAtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x05'}, + NodeType: statediff.Leaf, + LeafKey: minerLeafKey, + NodeValue: minerAccountAtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x0e'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account1LeafKey, + NodeValue: account1AtBlock2LeafNode, + StorageNodes: emptyStorage, + }, + { + Path: []byte{'\x06'}, + NodeType: statediff.Leaf, + LeafKey: contractLeafKey, + NodeValue: contractAccountAtBlock3LeafNode, + StorageNodes: []statediff.StorageNode{ + { + Path: []byte{}, + NodeType: statediff.Branch, + NodeValue: block3StorageBranchRootNode, + }, + { + Path: []byte{'\x02'}, + NodeType: statediff.Leaf, + LeafKey: slot0StorageKey.Bytes(), + NodeValue: slot0StorageLeafNode, + }, + { + Path: []byte{'\x0b'}, + NodeType: statediff.Leaf, + LeafKey: slot1StorageKey.Bytes(), + NodeValue: slot1StorageLeafNode, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: slot3StorageKey.Bytes(), + NodeValue: slot3StorageLeafNode, + }, + }, + }, + { + Path: []byte{'\x0c'}, + NodeType: statediff.Leaf, + LeafKey: testhelpers.Account2LeafKey, + NodeValue: account2AtBlock3LeafNode, + StorageNodes: emptyStorage, + }, + }, + }, + }, + } + + for _, test := range tests { + diff, err := builder.BuildStateTrieObject(test.block) + if err != nil { + t.Error(err) + } + receivedStateTrieRlp, err := rlp.EncodeToBytes(diff) + if err != nil { + t.Error(err) + } + expectedStateTrieRlp, err := rlp.EncodeToBytes(test.expected) + if err != nil { + t.Error(err) + } + sort.Slice(receivedStateTrieRlp, func(i, j int) bool { return receivedStateTrieRlp[i] < receivedStateTrieRlp[j] }) + sort.Slice(expectedStateTrieRlp, func(i, j int) bool { return expectedStateTrieRlp[i] < expectedStateTrieRlp[j] }) + if !bytes.Equal(receivedStateTrieRlp, expectedStateTrieRlp) { + t.Logf("Test failed: %s", test.name) + t.Errorf("actual state trie: %+v\r\n\r\n\r\nexpected state trie: %+v", diff, test.expected) + } + } +} + +/* +pragma solidity ^0.5.10; + +contract test { + address payable owner; + + modifier onlyOwner { + require( + msg.sender == owner, + "Only owner can call this function." + ); + _; + } + + uint256[100] data; + + constructor() public { + owner = msg.sender; + data = [1]; + } + + function Put(uint256 addr, uint256 value) public { + data[addr] = value; + } + + function close() public onlyOwner { //onlyOwner is custom modifier + selfdestruct(owner); // `owner` is the owners address + } +} +*/ diff --git a/pkg/helpers.go b/pkg/helpers.go new file mode 100644 index 0000000..05db7b0 --- /dev/null +++ b/pkg/helpers.go @@ -0,0 +1,96 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains a batch of utility type declarations used by the tests. As the node +// operates on unique types, a lot of them are needed to check various features. + +package statediff + +import ( + "fmt" + "sort" + "strings" +) + +func sortKeys(data AccountMap) []string { + keys := make([]string, 0, len(data)) + for key := range data { + keys = append(keys, key) + } + sort.Strings(keys) + + return keys +} + +// findIntersection finds the set of strings from both arrays that are equivalent +// a and b must first be sorted +// this is used to find which keys have been both "deleted" and "created" i.e. they were updated +func findIntersection(a, b []string) []string { + lenA := len(a) + lenB := len(b) + iOfA, iOfB := 0, 0 + updates := make([]string, 0) + if iOfA >= lenA || iOfB >= lenB { + return updates + } + for { + switch strings.Compare(a[iOfA], b[iOfB]) { + // -1 when a[iOfA] < b[iOfB] + case -1: + iOfA++ + if iOfA >= lenA { + return updates + } + // 0 when a[iOfA] == b[iOfB] + case 0: + updates = append(updates, a[iOfA]) + iOfA++ + iOfB++ + if iOfA >= lenA || iOfB >= lenB { + return updates + } + // 1 when a[iOfA] > b[iOfB] + case 1: + iOfB++ + if iOfB >= lenB { + return updates + } + } + } + +} + +// CheckKeyType checks what type of key we have +func CheckKeyType(elements []interface{}) (NodeType, error) { + if len(elements) > 2 { + return Branch, nil + } + if len(elements) < 2 { + return Unknown, fmt.Errorf("node cannot be less than two elements in length") + } + switch elements[0].([]byte)[0] / 16 { + case '\x00': + return Extension, nil + case '\x01': + return Extension, nil + case '\x02': + return Leaf, nil + case '\x03': + return Leaf, nil + default: + return Unknown, fmt.Errorf("unknown hex prefix") + } +} diff --git a/pkg/service.go b/pkg/service.go index f10a288..1223904 100644 --- a/pkg/service.go +++ b/pkg/service.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/statediff" "github.com/sirupsen/logrus" ) @@ -48,26 +47,26 @@ type IService interface { // Main event loop for processing state diffs Loop(wg *sync.WaitGroup) // Method to get state diff object at specific block - StateDiffAt(blockNumber uint64, params statediff.Params) (*statediff.Payload, error) + StateDiffAt(blockNumber uint64, params Params) (*Payload, error) // Method to get state trie object at specific block - StateTrieAt(blockNumber uint64, params statediff.Params) (*statediff.Payload, error) + StateTrieAt(blockNumber uint64, params Params) (*Payload, error) } // Service is the underlying struct for the state diffing service type Service struct { // Used to build the state diff objects - Builder statediff.Builder + Builder Builder // Used to read data from leveldb lvlDBReader lvlDBReader // Used to signal shutdown of the service QuitChan chan bool } -// NewStateDiffService creates a new statediff.Service +// NewStateDiffService creates a new Service func NewStateDiffService(lvlDBReader lvlDBReader) (*Service, error) { return &Service{ lvlDBReader: lvlDBReader, - Builder: statediff.NewBuilder(lvlDBReader.StateDB()), + Builder: NewBuilder(lvlDBReader.StateDB()), QuitChan: make(chan bool), }, nil } @@ -77,12 +76,12 @@ func (sds *Service) Protocols() []p2p.Protocol { return []p2p.Protocol{} } -// APIs returns the RPC descriptors the statediff.Service offers +// APIs returns the RPC descriptors the Service offers func (sds *Service) APIs() []rpc.API { return []rpc.API{ { - Namespace: statediff.APIName, - Version: statediff.APIVersion, + Namespace: APIName, + Version: APIVersion, Service: NewPublicStateDiffAPI(sds), Public: true, }, @@ -104,7 +103,7 @@ func (sds *Service) Loop(wg *sync.WaitGroup) { // StateDiffAt returns a state diff object payload at the specific blockheight // This operation cannot be performed back past the point of db pruning; it requires an archival node for historical data -func (sds *Service) StateDiffAt(blockNumber uint64, params statediff.Params) (*statediff.Payload, error) { +func (sds *Service) StateDiffAt(blockNumber uint64, params Params) (*Payload, error) { currentBlock, err := sds.lvlDBReader.GetBlockByNumber(blockNumber) if err != nil { return nil, err @@ -121,12 +120,12 @@ func (sds *Service) StateDiffAt(blockNumber uint64, params statediff.Params) (*s } // processStateDiff method builds the state diff payload from the current block, parent state root, and provided params -func (sds *Service) processStateDiff(currentBlock *types.Block, parentRoot common.Hash, params statediff.Params) (*statediff.Payload, error) { - stateDiff, err := sds.Builder.BuildStateDiffObject(statediff.Args{ - NewStateRoot: currentBlock.Root(), - OldStateRoot: parentRoot, +func (sds *Service) processStateDiff(currentBlock *types.Block, parentRoot common.Hash, params Params) (*Payload, error) { + stateDiff, err := sds.Builder.BuildStateDiffObject(Args{ BlockHash: currentBlock.Hash(), BlockNumber: currentBlock.Number(), + OldStateRoot: parentRoot, + NewStateRoot: currentBlock.Root(), }, params) if err != nil { return nil, err @@ -139,8 +138,8 @@ func (sds *Service) processStateDiff(currentBlock *types.Block, parentRoot commo return sds.newPayload(stateDiffRlp, currentBlock, params) } -func (sds *Service) newPayload(stateObject []byte, block *types.Block, params statediff.Params) (*statediff.Payload, error) { - payload := &statediff.Payload{ +func (sds *Service) newPayload(stateObject []byte, block *types.Block, params Params) (*Payload, error) { + payload := &Payload{ StateObjectRlp: stateObject, } if params.IncludeBlock { @@ -173,7 +172,7 @@ func (sds *Service) newPayload(stateObject []byte, block *types.Block, params st // StateTrieAt returns a state trie object payload at the specified blockheight // This operation cannot be performed back past the point of db pruning; it requires an archival node for historical data -func (sds *Service) StateTrieAt(blockNumber uint64, params statediff.Params) (*statediff.Payload, error) { +func (sds *Service) StateTrieAt(blockNumber uint64, params Params) (*Payload, error) { currentBlock, err := sds.lvlDBReader.GetBlockByNumber(blockNumber) if err != nil { return nil, err @@ -182,7 +181,7 @@ func (sds *Service) StateTrieAt(blockNumber uint64, params statediff.Params) (*s return sds.processStateTrie(currentBlock, params) } -func (sds *Service) processStateTrie(block *types.Block, params statediff.Params) (*statediff.Payload, error) { +func (sds *Service) processStateTrie(block *types.Block, params Params) (*Payload, error) { stateNodes, err := sds.Builder.BuildStateTrieObject(block) if err != nil { return nil, err diff --git a/pkg/state_types.go b/pkg/state_types.go new file mode 100644 index 0000000..0c86e49 --- /dev/null +++ b/pkg/state_types.go @@ -0,0 +1,47 @@ +package statediff + +// Wrapper types for state trie + +import ( + "github.com/ethereum/go-ethereum/core/state" +) + +// StateNode holds the data for a single state diff node +type StateNode struct { + NodeType NodeType `json:"nodeType" gencodec:"required"` + Path []byte `json:"path" gencodec:"required"` + NodeValue []byte `json:"value" gencodec:"required"` + StorageNodes []StorageNode `json:"storage"` + LeafKey []byte `json:"leafKey"` +} + +// StorageNode holds the data for a single storage diff node +type StorageNode struct { + NodeType NodeType `json:"nodeType" gencodec:"required"` + Path []byte `json:"path" gencodec:"required"` + NodeValue []byte `json:"value" gencodec:"required"` + LeafKey []byte `json:"leafKey"` +} + +// AccountMap is a mapping of hex encoded path => account wrapper +type AccountMap map[string]accountWrapper + +// accountWrapper is used to temporary associate the unpacked node with its raw values +type accountWrapper struct { + Account *state.Account + NodeType NodeType + Path []byte + NodeValue []byte + LeafKey []byte +} + +// NodeType for explicitly setting type of node +type NodeType string + +const ( + Unknown NodeType = "Unknown" + Leaf NodeType = "Leaf" + Extension NodeType = "Extension" + Branch NodeType = "Branch" + Removed NodeType = "Removed" // used to represent pathes which have been emptied +) diff --git a/pkg/testhelpers/helpers.go b/pkg/testhelpers/helpers.go new file mode 100644 index 0000000..ab141c9 --- /dev/null +++ b/pkg/testhelpers/helpers.go @@ -0,0 +1,124 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package testhelpers + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// MakeChain creates a chain of n blocks starting at and including parent. +// the returned hash chain is ordered head->parent. +func MakeChain(n int, parent *types.Block, chainGen func(int, *core.BlockGen)) ([]*types.Block, *core.BlockChain) { + config := params.TestChainConfig + blocks, _ := core.GenerateChain(config, parent, ethash.NewFaker(), Testdb, n, chainGen) + chain, _ := core.NewBlockChain(Testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil) + return blocks, chain +} + +func TestSelfDestructChainGen(i int, block *core.BlockGen) { + signer := types.HomesteadSigner{} + switch i { + case 0: + // Block 1 is mined by Account1Addr + // Account1Addr creates a new contract + block.SetCoinbase(TestBankAddress) + tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 1000000, big.NewInt(0), ContractCode), signer, TestBankKey) + ContractAddr = crypto.CreateAddress(TestBankAddress, 0) + block.AddTx(tx) + case 1: + // Block 2 is mined by Account1Addr + // Account1Addr self-destructs the contract + block.SetCoinbase(TestBankAddress) + data := common.Hex2Bytes("43D726D6") + tx, _ := types.SignTx(types.NewTransaction(1, ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey) + block.AddTx(tx) + } +} + +func TestChainGen(i int, block *core.BlockGen) { + signer := types.HomesteadSigner{} + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), Account1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, TestBankKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // Account1Addr passes it on to account #2. + // Account1Addr creates a test contract. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), Account1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, TestBankKey) + nonce := block.TxNonce(Account1Addr) + tx2, _ := types.SignTx(types.NewTransaction(nonce, Account2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, Account1Key) + nonce++ + tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), ContractCode), signer, Account1Key) + ContractAddr = crypto.CreateAddress(Account1Addr, nonce) + block.AddTx(tx1) + block.AddTx(tx2) + block.AddTx(tx3) + case 2: + // Block 3 has a single tx from the bankAccount to the contract, that transfers no value + // Block 3 is mined by Account2Addr + block.SetCoinbase(Account2Addr) + //put function: c16431b9 + //close function: 43d726d6 + data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey) + block.AddTx(tx) + case 3: + // Block 4 has three txs from bankAccount to the contract, that transfer no value + // Two set the two original slot positions to 0 and one sets another position to a new value + // Block 4 is mined by Account2Addr + block.SetCoinbase(Account2Addr) + data1 := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + data2 := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000") + data3 := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000009") + + nonce := block.TxNonce(TestBankAddress) + tx1, _ := types.SignTx(types.NewTransaction(nonce, ContractAddr, big.NewInt(0), 100000, nil, data1), signer, TestBankKey) + nonce++ + tx2, _ := types.SignTx(types.NewTransaction(nonce, ContractAddr, big.NewInt(0), 100000, nil, data2), signer, TestBankKey) + nonce++ + tx3, _ := types.SignTx(types.NewTransaction(nonce, ContractAddr, big.NewInt(0), 100000, nil, data3), signer, TestBankKey) + block.AddTx(tx1) + block.AddTx(tx2) + block.AddTx(tx3) + case 4: + // Block 5 has one tx from bankAccount to the contract, that transfers no value + // It sets the remaining storage value to zero + // Block 5 is mined by Account1Addr + block.SetCoinbase(Account1Addr) + data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000") + nonce := block.TxNonce(TestBankAddress) + tx, _ := types.SignTx(types.NewTransaction(nonce, ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey) + block.AddTx(tx) + case 5: + // Block 6 has a tx from Account1Key which self-destructs the contract, it transfers no value + // Block 6 is mined by Account2Addr + block.SetCoinbase(Account2Addr) + data := common.Hex2Bytes("43D726D6") + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(Account1Addr), ContractAddr, big.NewInt(0), 100000, nil, data), signer, Account1Key) + block.AddTx(tx) + } +} diff --git a/pkg/testhelpers/mocks/blockchain.go b/pkg/testhelpers/mocks/blockchain.go new file mode 100644 index 0000000..a995e47 --- /dev/null +++ b/pkg/testhelpers/mocks/blockchain.go @@ -0,0 +1,128 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package mocks + +import ( + "errors" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// BlockChain is a mock blockchain for testing +type BlockChain struct { + HashesLookedUp []common.Hash + blocksToReturnByHash map[common.Hash]*types.Block + blocksToReturnByNumber map[uint64]*types.Block + callCount int + ChainEvents []core.ChainEvent + Receipts map[common.Hash]types.Receipts + TDByHash map[common.Hash]*big.Int +} + +// SetBlocksForHashes mock method +func (blockChain *BlockChain) SetBlocksForHashes(blocks map[common.Hash]*types.Block) { + if blockChain.blocksToReturnByHash == nil { + blockChain.blocksToReturnByHash = make(map[common.Hash]*types.Block) + } + blockChain.blocksToReturnByHash = blocks +} + +// GetBlockByHash mock method +func (blockChain *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { + blockChain.HashesLookedUp = append(blockChain.HashesLookedUp, hash) + + var block *types.Block + if len(blockChain.blocksToReturnByHash) > 0 { + block = blockChain.blocksToReturnByHash[hash] + } + + return block +} + +// SetChainEvents mock method +func (blockChain *BlockChain) SetChainEvents(chainEvents []core.ChainEvent) { + blockChain.ChainEvents = chainEvents +} + +// SubscribeChainEvent mock method +func (blockChain *BlockChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + subErr := errors.New("subscription error") + + var eventCounter int + subscription := event.NewSubscription(func(quit <-chan struct{}) error { + for _, chainEvent := range blockChain.ChainEvents { + if eventCounter > 1 { + time.Sleep(250 * time.Millisecond) + return subErr + } + select { + case ch <- chainEvent: + case <-quit: + return nil + } + eventCounter++ + } + return nil + }) + + return subscription +} + +// SetReceiptsForHash test method +func (blockChain *BlockChain) SetReceiptsForHash(hash common.Hash, receipts types.Receipts) { + if blockChain.Receipts == nil { + blockChain.Receipts = make(map[common.Hash]types.Receipts) + } + blockChain.Receipts[hash] = receipts +} + +// GetReceiptsByHash mock method +func (blockChain *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { + return blockChain.Receipts[hash] +} + +// SetBlockForNumber test method +func (blockChain *BlockChain) SetBlockForNumber(block *types.Block, number uint64) { + if blockChain.blocksToReturnByNumber == nil { + blockChain.blocksToReturnByNumber = make(map[uint64]*types.Block) + } + blockChain.blocksToReturnByNumber[number] = block +} + +// GetBlockByNumber mock method +func (blockChain *BlockChain) GetBlockByNumber(number uint64) *types.Block { + return blockChain.blocksToReturnByNumber[number] +} + +// GetTdByHash mock method +func (blockChain *BlockChain) GetTdByHash(hash common.Hash) *big.Int { + return blockChain.TDByHash[hash] +} + +func (blockChain *BlockChain) SetTdByHash(hash common.Hash, td *big.Int) { + if blockChain.TDByHash == nil { + blockChain.TDByHash = make(map[common.Hash]*big.Int) + } + blockChain.TDByHash[hash] = td +} + +func (blockChain *BlockChain) UnlockTrie(root common.Hash) {} diff --git a/pkg/testhelpers/mocks/builder.go b/pkg/testhelpers/mocks/builder.go new file mode 100644 index 0000000..4ca0729 --- /dev/null +++ b/pkg/testhelpers/mocks/builder.go @@ -0,0 +1,57 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package mocks + +import ( + "github.com/ethereum/go-ethereum/core/types" + statediff "github.com/vulcanize/eth-statediff-service/pkg" +) + +// Builder is a mock state diff builder +type Builder struct { + Args statediff.Args + Params statediff.Params + stateDiff statediff.StateObject + block *types.Block + stateTrie statediff.StateObject + builderError error +} + +// BuildStateDiffObject mock method +func (builder *Builder) BuildStateDiffObject(args statediff.Args, params statediff.Params) (statediff.StateObject, error) { + builder.Args = args + builder.Params = params + + return builder.stateDiff, builder.builderError +} + +// BuildStateTrieObject mock method +func (builder *Builder) BuildStateTrieObject(block *types.Block) (statediff.StateObject, error) { + builder.block = block + + return builder.stateTrie, builder.builderError +} + +// SetStateDiffToBuild mock method +func (builder *Builder) SetStateDiffToBuild(stateDiff statediff.StateObject) { + builder.stateDiff = stateDiff +} + +// SetBuilderError mock method +func (builder *Builder) SetBuilderError(err error) { + builder.builderError = err +} diff --git a/pkg/testhelpers/test_data.go b/pkg/testhelpers/test_data.go new file mode 100644 index 0000000..38273e1 --- /dev/null +++ b/pkg/testhelpers/test_data.go @@ -0,0 +1,71 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package testhelpers + +import ( + "math/big" + "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +// AddressToLeafKey hashes an returns an address +func AddressToLeafKey(address common.Address) []byte { + return crypto.Keccak256(address[:]) +} + +// AddressToEncodedPath hashes an address and appends the even-number leaf flag to it +func AddressToEncodedPath(address common.Address) []byte { + addrHash := crypto.Keccak256(address[:]) + decodedPath := append(EvenLeafFlag, addrHash...) + return decodedPath +} + +// Test variables +var ( + EvenLeafFlag = []byte{byte(2) << 4} + BlockNumber = big.NewInt(rand.Int63()) + BlockHash = "0xfa40fbe2d98d98b3363a778d52f2bcd29d6790b9b3f3cab2b167fd12d3550f73" + NullCodeHash = crypto.Keccak256Hash([]byte{}) + StoragePath = common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes() + StorageKey = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001").Bytes() + StorageValue = common.Hex2Bytes("0x03") + NullHash = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000") + + Testdb = rawdb.NewMemoryDatabase() + TestBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + TestBankAddress = crypto.PubkeyToAddress(TestBankKey.PublicKey) //0x71562b71999873DB5b286dF957af199Ec94617F7 + BankLeafKey = AddressToLeafKey(TestBankAddress) + TestBankFunds = big.NewInt(100000000) + Genesis = core.GenesisBlockForTesting(Testdb, TestBankAddress, TestBankFunds) + + Account1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + Account2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + Account1Addr = crypto.PubkeyToAddress(Account1Key.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7 + Account2Addr = crypto.PubkeyToAddress(Account2Key.PublicKey) //0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e + Account1LeafKey = AddressToLeafKey(Account1Addr) + Account2LeafKey = AddressToLeafKey(Account2Addr) + ContractCode = common.Hex2Bytes("608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040518060200160405280600160ff16815250600190600161007492919061007a565b506100e4565b82606481019282156100ae579160200282015b828111156100ad578251829060ff1690559160200191906001019061008d565b5b5090506100bb91906100bf565b5090565b6100e191905b808211156100dd5760008160009055506001016100c5565b5090565b90565b6101ca806100f36000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806343d726d61461003b578063c16431b914610045575b600080fd5b61004361007d565b005b61007b6004803603604081101561005b57600080fd5b81019080803590602001909291908035906020019092919050505061015c565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610122576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101746022913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b806001836064811061016a57fe5b0181905550505056fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e2ea265627a7a72305820e3747183708fb6bff3f6f7a80fb57dcc1c19f83f9cb25457a3ed5c0424bde66864736f6c634300050a0032") + ContractAddr common.Address + + EmptyRootNode, _ = rlp.EncodeToBytes([]byte{}) + EmptyContractRoot = crypto.Keccak256Hash(EmptyRootNode) +) diff --git a/pkg/types.go b/pkg/types.go new file mode 100644 index 0000000..0da8be8 --- /dev/null +++ b/pkg/types.go @@ -0,0 +1,86 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains a batch of utility type declarations used by the tests. As the node +// operates on unique types, a lot of them are needed to check various features. + +package statediff + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Subscription struct holds our subscription channels +type Subscription struct { + PayloadChan chan<- Payload + QuitChan chan<- bool +} + +// Params is used to carry in parameters from subscribing/requesting clients configuration +type Params struct { + IntermediateStateNodes bool + IntermediateStorageNodes bool + IncludeBlock bool + IncludeReceipts bool + IncludeTD bool + WatchedAddresses []common.Address + WatchedStorageSlots []common.Hash +} + +// Args bundles the arguments for the state diff builder +type Args struct { + OldStateRoot, NewStateRoot, BlockHash common.Hash + BlockNumber *big.Int +} + +// Payload packages the data to send to statediff subscriptions +type Payload struct { + BlockRlp []byte `json:"blockRlp"` + TotalDifficulty *big.Int `json:"totalDifficulty"` + ReceiptsRlp []byte `json:"receiptsRlp"` + StateObjectRlp []byte `json:"stateObjectRlp" gencodec:"required"` + + encoded []byte + err error +} + +func (sd *Payload) ensureEncoded() { + if sd.encoded == nil && sd.err == nil { + sd.encoded, sd.err = json.Marshal(sd) + } +} + +// Length to implement Encoder interface for Payload +func (sd *Payload) Length() int { + sd.ensureEncoded() + return len(sd.encoded) +} + +// Encode to implement Encoder interface for Payload +func (sd *Payload) Encode() ([]byte, error) { + sd.ensureEncoded() + return sd.encoded, sd.err +} + +// StateObject is the final output structure from the builder +type StateObject struct { + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + BlockNumber *big.Int `json:"blockNumber" gencodec:"required"` + Nodes []StateNode `json:"nodes" gencodec:"required"` +}