diff --git a/statediff/api.go b/statediff/api.go index 275867e50..3be07b73e 100644 --- a/statediff/api.go +++ b/statediff/api.go @@ -90,7 +90,12 @@ func (api *PublicStateDiffAPI) Stream(ctx context.Context, params Params) (*rpc. return rpcSub, nil } -// StateDiffAt returns a statediff payload at the specific blockheight +// StateDiffAt returns a state diff payload at the specific blockheight 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 Params) (*Payload, error) { + return api.sds.StateTrieAt(blockNumber, params) +} diff --git a/statediff/builder.go b/statediff/builder.go index 2924716de..9b7f2baea 100644 --- a/statediff/builder.go +++ b/statediff/builder.go @@ -23,6 +23,8 @@ import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" @@ -31,11 +33,16 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -var nullNode = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") +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 { - BuildStateDiff(args Args, params Params) (StateDiff, error) + BuildStateDiffObject(args Args, params Params) (StateObject, error) + BuildStateTrieObject(current *types.Block) (StateObject, error) } type builder struct { @@ -49,23 +56,99 @@ func NewBuilder(stateCache state.Database) Builder { } } -// BuildStateDiff builds a statediff object from two blocks and the provided parameters -func (sdb *builder) BuildStateDiff(args Args, params Params) (StateDiff, error) { +// 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:] + storageDiffs, 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, + StorageDiffs: storageDiffs, + }) + 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, nil +} + +// 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) (StateDiff, error) { +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 StateDiff{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) + return StateObject{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) } newTrie, err := sdb.stateCache.OpenTrie(args.NewStateRoot) if err != nil { - return StateDiff{}, fmt.Errorf("error creating trie for newStateRoot: %v", err) + return StateObject{}, fmt.Errorf("error creating trie for newStateRoot: %v", err) } // collect a slice of all the intermediate nodes that were touched and exist at B @@ -73,14 +156,14 @@ func (sdb *builder) buildStateDiffWithIntermediateStateNodes(args Args, params P // and a slice of all the paths for the nodes in both of the above sets createdOrUpdatedIntermediateNodes, diffAccountsAtB, diffPathsAtB, err := sdb.createdAndUpdatedState(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{})) if err != nil { - return StateDiff{}, fmt.Errorf("error collecting createdAndUpdatedNodes: %v", err) + 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 deletedNodes, diffAccountsAtA, err := sdb.deletedOrUpdatedState(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), diffPathsAtB) if err != nil { - return StateDiff{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) + return StateObject{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) } // collect and sort the leafkey keys for both account mappings into a slice @@ -89,50 +172,50 @@ func (sdb *builder) buildStateDiffWithIntermediateStateNodes(args Args, params P // 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 + // 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 StateDiff{}, fmt.Errorf("error building diff for updated accounts: %v", err) + 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 StateDiff{}, fmt.Errorf("error building diff for created accounts: %v", err) + 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 StateDiff{ + return StateObject{ BlockNumber: args.BlockNumber, BlockHash: args.BlockHash, Nodes: append(append(append(updatedAccounts, createdAccounts...), createdOrUpdatedIntermediateNodes...), deletedNodes...), }, nil } -func (sdb *builder) buildStateDiffWithoutIntermediateStateNodes(args Args, params Params) (StateDiff, error) { +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 StateDiff{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) + return StateObject{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err) } newTrie, err := sdb.stateCache.OpenTrie(args.NewStateRoot) if err != nil { - return StateDiff{}, fmt.Errorf("error creating trie for newStateRoot: %v", err) + 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 diffAccountsAtB, err := sdb.collectDiffAccounts(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}), params.WatchedAddresses) if err != nil { - return StateDiff{}, fmt.Errorf("error collecting createdAndUpdatedNodes: %v", err) + return StateObject{}, fmt.Errorf("error collecting createdAndUpdatedNodes: %v", err) } // collect a map of their leafkey to all the accounts that were touched and exist at A diffAccountsAtA, err := sdb.collectDiffAccounts(newTrie.NodeIterator([]byte{}), oldTrie.NodeIterator([]byte{}), params.WatchedAddresses) if err != nil { - return StateDiff{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) + return StateObject{}, fmt.Errorf("error collecting deletedOrUpdatedNodes: %v", err) } // collect and sort the leafkeys for both account mappings into a slice @@ -148,21 +231,21 @@ func (sdb *builder) buildStateDiffWithoutIntermediateStateNodes(args Args, param // 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 StateDiff{}, fmt.Errorf("error building diff for updated accounts: %v", err) + 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 StateDiff{}, fmt.Errorf("error building diff for created accounts: %v", err) + return StateObject{}, fmt.Errorf("error building diff for created accounts: %v", err) } // build the diff nodes for deleted accounts deletedAccounts, err := sdb.buildAccountDeletions(diffAccountsAtA) if err != nil { - return StateDiff{}, fmt.Errorf("error building diff for deleted accounts: %v", err) + return StateObject{}, fmt.Errorf("error building diff for deleted accounts: %v", err) } // assemble all of the nodes into the statediff object - return StateDiff{ + return StateObject{ BlockNumber: args.BlockNumber, BlockHash: args.BlockHash, Nodes: append(append(updatedAccounts, createdAccounts...), deletedAccounts...), @@ -179,7 +262,7 @@ func (sdb *builder) collectDiffAccounts(a, b trie.NodeIterator, watchedAddresses if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) @@ -239,7 +322,7 @@ func (sdb *builder) createdAndUpdatedState(a, b trie.NodeIterator) ([]StateNode, if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) @@ -303,7 +386,7 @@ func (sdb *builder) deletedOrUpdatedState(a, b trie.NodeIterator, diffPathsAtB m if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) @@ -429,6 +512,9 @@ func (sdb *builder) buildAccountDeletions(accounts AccountMap) ([]StateNode, err // 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 { @@ -449,7 +535,7 @@ func (sdb *builder) buildStorageNodesFromTrie(it trie.NodeIterator, watchedStora if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) @@ -497,6 +583,9 @@ func (sdb *builder) buildStorageNodesFromTrie(it trie.NodeIterator, watchedStora // 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 { @@ -527,7 +616,7 @@ func (sdb *builder) createdAndUpdatedStorage(a, b trie.NodeIterator, watchedKeys if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) @@ -582,7 +671,7 @@ func (sdb *builder) deletedOrUpdatedStorage(a, b trie.NodeIterator, diffPathsAtB if it.Leaf() { continue } - if bytes.Equal(nullNode, it.Hash().Bytes()) { + if bytes.Equal(nullHashBytes, it.Hash().Bytes()) { continue } nodePath := make([]byte, len(it.Path())) diff --git a/statediff/builder_test.go b/statediff/builder_test.go index f5439eae8..62633f416 100644 --- a/statediff/builder_test.go +++ b/statediff/builder_test.go @@ -477,7 +477,7 @@ func TestBuilder(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testEmptyDiff", @@ -487,7 +487,7 @@ func TestBuilder(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -502,7 +502,7 @@ func TestBuilder(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: []statediff.StateNode{ @@ -525,7 +525,7 @@ func TestBuilder(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -564,7 +564,7 @@ func TestBuilder(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -629,7 +629,7 @@ func TestBuilder(t *testing.T) { BlockNumber: block3.Number(), BlockHash: block3.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block3.Number(), BlockHash: block3.Hash(), Nodes: []statediff.StateNode{ @@ -667,7 +667,7 @@ func TestBuilder(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -706,7 +706,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testEmptyDiff", @@ -716,7 +716,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -731,7 +731,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: []statediff.StateNode{ @@ -754,7 +754,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -799,7 +799,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -875,7 +875,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { BlockNumber: block3.Number(), BlockHash: block3.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block3.Number(), BlockHash: block3.Hash(), Nodes: []statediff.StateNode{ @@ -924,7 +924,7 @@ func TestBuilderWithIntermediateNodes(t *testing.T) { } for i, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -975,7 +975,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testEmptyDiff", @@ -985,7 +985,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -1000,7 +1000,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -1015,7 +1015,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -1039,7 +1039,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -1083,7 +1083,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { BlockNumber: block3.Number(), BlockHash: block3.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block3.Number(), BlockHash: block3.Hash(), Nodes: []statediff.StateNode{ @@ -1107,7 +1107,7 @@ func TestBuilderWithWatchedAddressList(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -1145,7 +1145,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testEmptyDiff", @@ -1155,7 +1155,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -1170,7 +1170,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { BlockNumber: block0.Number(), BlockHash: block0.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block0.Number(), BlockHash: block0.Hash(), Nodes: emptyDiffs, @@ -1185,7 +1185,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -1209,7 +1209,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -1247,7 +1247,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { BlockNumber: block3.Number(), BlockHash: block3.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block3.Number(), BlockHash: block3.Hash(), Nodes: []statediff.StateNode{ @@ -1264,7 +1264,7 @@ func TestBuilderWithWatchedAddressAndStorageKeyList(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -1302,7 +1302,7 @@ func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ // blocks 0-3 are the same as in TestBuilderWithIntermediateNodes { @@ -1313,7 +1313,7 @@ func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { BlockNumber: block4.Number(), BlockHash: block4.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block4.Number(), BlockHash: block4.Hash(), Nodes: []statediff.StateNode{ @@ -1377,7 +1377,7 @@ func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { BlockNumber: block5.Number(), BlockHash: block5.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block5.Number(), BlockHash: block5.Hash(), Nodes: []statediff.StateNode{ @@ -1436,7 +1436,7 @@ func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { BlockNumber: block6.Number(), BlockHash: block6.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block6.Number(), BlockHash: block6.Hash(), Nodes: []statediff.StateNode{ @@ -1471,7 +1471,7 @@ func TestBuilderWithRemovedAccountAndStorage(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -1509,7 +1509,7 @@ func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing. var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ // blocks 0-3 are the same as in TestBuilderWithIntermediateNodes { @@ -1520,7 +1520,7 @@ func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing. BlockNumber: block4.Number(), BlockHash: block4.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block4.Number(), BlockHash: block4.Hash(), Nodes: []statediff.StateNode{ @@ -1573,7 +1573,7 @@ func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing. BlockNumber: block5.Number(), BlockHash: block5.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block5.Number(), BlockHash: block5.Hash(), Nodes: []statediff.StateNode{ @@ -1626,7 +1626,7 @@ func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing. BlockNumber: block6.Number(), BlockHash: block6.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block6.Number(), BlockHash: block6.Hash(), Nodes: []statediff.StateNode{ @@ -1655,7 +1655,7 @@ func TestBuilderWithRemovedAccountAndStorageWithoutIntermediateNodes(t *testing. } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } @@ -1773,7 +1773,7 @@ func TestBuilderWithMovedAccount(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testBlock1", @@ -1783,7 +1783,7 @@ func TestBuilderWithMovedAccount(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -1836,7 +1836,7 @@ func TestBuilderWithMovedAccount(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -1863,7 +1863,7 @@ func TestBuilderWithMovedAccount(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } diff --git a/statediff/service.go b/statediff/service.go index 505e6f8ce..6e065d9fe 100644 --- a/statediff/service.go +++ b/statediff/service.go @@ -56,8 +56,10 @@ type IService interface { Subscribe(id rpc.ID, sub chan<- Payload, quitChan chan<- bool, params Params) // Method to unsubscribe from state diff processing Unsubscribe(id rpc.ID) error - // Method to get statediff at specific block + // Method to get state diff object at specific block StateDiffAt(blockNumber uint64, params Params) (*Payload, error) + // Method to get state trie object at specific block + StateTrieAt(blockNumber uint64, params Params) (*Payload, error) } // Service is the underlying struct for the state diffing service @@ -181,7 +183,7 @@ func (sds *Service) streamStateDiff(currentBlock *types.Block, parentRoot common // 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 Params) (*Payload, error) { - stateDiff, err := sds.Builder.BuildStateDiff(Args{ + stateDiff, err := sds.Builder.BuildStateDiffObject(Args{ NewStateRoot: currentBlock.Root(), OldStateRoot: parentRoot, BlockHash: currentBlock.Hash(), @@ -195,7 +197,7 @@ func (sds *Service) processStateDiff(currentBlock *types.Block, parentRoot commo return nil, err } payload := Payload{ - StateDiffRlp: stateDiffRlp, + StateObjectRlp: stateDiffRlp, } if params.IncludeBlock { blockBuff := new(bytes.Buffer) @@ -275,8 +277,8 @@ func (sds *Service) Start(*p2p.Server) error { return nil } -// StateDiffAt returns a statediff payload at the specific blockheight -// This operation cannot be performed back past the point of db pruning; it requires an archival node +// 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 Params) (*Payload, error) { currentBlock := sds.BlockChain.GetBlockByNumber(blockNumber) log.Info(fmt.Sprintf("sending state diff at %d", blockNumber)) @@ -287,6 +289,47 @@ func (sds *Service) StateDiffAt(blockNumber uint64, params Params) (*Payload, er return sds.processStateDiff(currentBlock, parentBlock.Root(), params) } +// 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 Params) (*Payload, error) { + currentBlock := sds.BlockChain.GetBlockByNumber(blockNumber) + log.Info(fmt.Sprintf("sending state trie at %d", blockNumber)) + return sds.stateTrieAt(currentBlock, params) +} + +func (sds *Service) stateTrieAt(block *types.Block, params Params) (*Payload, error) { + stateNodes, err := sds.Builder.BuildStateTrieObject(block) + if err != nil { + return nil, err + } + stateTrieRlp, err := rlp.EncodeToBytes(stateNodes) + if err != nil { + return nil, err + } + payload := Payload{ + StateObjectRlp: stateTrieRlp, + } + if params.IncludeBlock { + blockBuff := new(bytes.Buffer) + if err = block.EncodeRLP(blockBuff); err != nil { + return nil, err + } + payload.BlockRlp = blockBuff.Bytes() + } + if params.IncludeTD { + payload.TotalDifficulty = sds.BlockChain.GetTdByHash(block.Hash()) + } + if params.IncludeReceipts { + receiptBuff := new(bytes.Buffer) + receipts := sds.BlockChain.GetReceiptsByHash(block.Hash()) + if err = rlp.Encode(receiptBuff, receipts); err != nil { + return nil, err + } + payload.ReceiptsRlp = receiptBuff.Bytes() + } + return &payload, nil +} + // Stop is used to close down the service func (sds *Service) Stop() error { log.Info("Stopping statediff service") diff --git a/statediff/service_test.go b/statediff/service_test.go index 1c4475521..f0262e11a 100644 --- a/statediff/service_test.go +++ b/statediff/service_test.go @@ -214,7 +214,7 @@ func TestGetStateDiffAt(t *testing.T) { } func testErrorInStateDiffAt(t *testing.T) { - mockStateDiff := statediff.StateDiff{ + mockStateDiff := statediff.StateObject{ BlockNumber: testBlock1.Number(), BlockHash: testBlock1.Hash(), } @@ -231,9 +231,9 @@ func testErrorInStateDiffAt(t *testing.T) { t.Error(err) } expectedStateDiffPayload := statediff.Payload{ - StateDiffRlp: expectedStateDiffRlp, - ReceiptsRlp: expectedReceiptsRlp, - BlockRlp: expectedBlockRlp, + StateObjectRlp: expectedStateDiffRlp, + ReceiptsRlp: expectedReceiptsRlp, + BlockRlp: expectedBlockRlp, } expectedStateDiffPayloadRlp, err := rlp.EncodeToBytes(expectedStateDiffPayload) if err != nil { diff --git a/statediff/test_data/builder_test.go b/statediff/test_data/builder_test.go index 4fbf9a248..54f2185ca 100644 --- a/statediff/test_data/builder_test.go +++ b/statediff/test_data/builder_test.go @@ -477,7 +477,7 @@ func TestBuilderOnMainnetBlocks(t *testing.T) { var tests = []struct { name string startingArguments statediff.Args - expected *statediff.StateDiff + expected *statediff.StateObject }{ { "testBlock1", @@ -488,7 +488,7 @@ func TestBuilderOnMainnetBlocks(t *testing.T) { BlockNumber: block1.Number(), BlockHash: block1.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -531,7 +531,7 @@ func TestBuilderOnMainnetBlocks(t *testing.T) { BlockNumber: block2.Number(), BlockHash: block2.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block2.Number(), BlockHash: block2.Hash(), Nodes: []statediff.StateNode{ @@ -589,7 +589,7 @@ func TestBuilderOnMainnetBlocks(t *testing.T) { BlockNumber: block3.Number(), BlockHash: block3.Hash(), }, - &statediff.StateDiff{ + &statediff.StateObject{ BlockNumber: block3.Number(), BlockHash: block3.Hash(), Nodes: []statediff.StateNode{ @@ -662,7 +662,7 @@ func TestBuilderOnMainnetBlocks(t *testing.T) { } for _, test := range tests { - diff, err := builder.BuildStateDiff(test.startingArguments, params) + diff, err := builder.BuildStateDiffObject(test.startingArguments, params) if err != nil { t.Error(err) } diff --git a/statediff/testhelpers/mocks/api.go b/statediff/testhelpers/mocks/api.go index 8e9f840f9..bac3b7200 100644 --- a/statediff/testhelpers/mocks/api.go +++ b/statediff/testhelpers/mocks/api.go @@ -120,7 +120,7 @@ func (sds *MockStateDiffService) streamStateDiff(currentBlock *types.Block, pare // processStateDiff method builds the state diff payload from the current block, parent state root, and provided params func (sds *MockStateDiffService) processStateDiff(currentBlock *types.Block, parentRoot common.Hash, params statediff.Params) (*statediff.Payload, error) { - stateDiff, err := sds.Builder.BuildStateDiff(statediff.Args{ + stateDiff, err := sds.Builder.BuildStateDiffObject(statediff.Args{ NewStateRoot: currentBlock.Root(), OldStateRoot: parentRoot, BlockHash: currentBlock.Hash(), @@ -134,7 +134,7 @@ func (sds *MockStateDiffService) processStateDiff(currentBlock *types.Block, par return nil, err } payload := statediff.Payload{ - StateDiffRlp: stateDiffRlp, + StateObjectRlp: stateDiffRlp, } if params.IncludeBlock { blockBuff := new(bytes.Buffer) @@ -204,6 +204,46 @@ func (sds *MockStateDiffService) StateDiffAt(blockNumber uint64, params statedif return sds.processStateDiff(currentBlock, parentBlock.Root(), params) } +// StateTrieAt mock method +func (sds *MockStateDiffService) StateTrieAt(blockNumber uint64, params statediff.Params) (*statediff.Payload, error) { + currentBlock := sds.BlockChain.GetBlockByNumber(blockNumber) + log.Info(fmt.Sprintf("sending state trie at %d", blockNumber)) + return sds.stateTrieAt(currentBlock, params) +} + +func (sds *MockStateDiffService) stateTrieAt(block *types.Block, params statediff.Params) (*statediff.Payload, error) { + stateNodes, err := sds.Builder.BuildStateTrieObject(block) + if err != nil { + return nil, err + } + stateTrieRlp, err := rlp.EncodeToBytes(stateNodes) + if err != nil { + return nil, err + } + payload := statediff.Payload{ + StateObjectRlp: stateTrieRlp, + } + if params.IncludeBlock { + blockBuff := new(bytes.Buffer) + if err = block.EncodeRLP(blockBuff); err != nil { + return nil, err + } + payload.BlockRlp = blockBuff.Bytes() + } + if params.IncludeTD { + payload.TotalDifficulty = sds.BlockChain.GetTdByHash(block.Hash()) + } + if params.IncludeReceipts { + receiptBuff := new(bytes.Buffer) + receipts := sds.BlockChain.GetReceiptsByHash(block.Hash()) + if err = rlp.Encode(receiptBuff, receipts); err != nil { + return nil, err + } + payload.ReceiptsRlp = receiptBuff.Bytes() + } + return &payload, nil +} + // close is used to close all listening subscriptions func (sds *MockStateDiffService) close() { sds.Lock() diff --git a/statediff/testhelpers/mocks/api_test.go b/statediff/testhelpers/mocks/api_test.go index 32d6e0b21..81e292821 100644 --- a/statediff/testhelpers/mocks/api_test.go +++ b/statediff/testhelpers/mocks/api_test.go @@ -91,7 +91,7 @@ func testSubscriptionAPI(t *testing.T) { BlockHash: block1.Hash(), } expectedReceiptBytes, _ := rlp.EncodeToBytes(types.Receipts{mockReceipt}) - expectedStateDiff := statediff.StateDiff{ + expectedStateDiff := statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -149,9 +149,9 @@ func testSubscriptionAPI(t *testing.T) { if !bytes.Equal(payload.BlockRlp, expectedBlockRlp) { t.Errorf("payload does not have expected block\r\nactual block rlp: %v\r\nexpected block rlp: %v", payload.BlockRlp, expectedBlockRlp) } - sort.Slice(payload.StateDiffRlp, func(i, j int) bool { return payload.StateDiffRlp[i] < payload.StateDiffRlp[j] }) - if !bytes.Equal(payload.StateDiffRlp, expectedStateDiffBytes) { - t.Errorf("payload does not have expected state diff\r\nactual state diff rlp: %v\r\nexpected state diff rlp: %v", payload.StateDiffRlp, expectedStateDiffBytes) + sort.Slice(payload.StateObjectRlp, func(i, j int) bool { return payload.StateObjectRlp[i] < payload.StateObjectRlp[j] }) + if !bytes.Equal(payload.StateObjectRlp, expectedStateDiffBytes) { + t.Errorf("payload does not have expected state diff\r\nactual state diff rlp: %v\r\nexpected state diff rlp: %v", payload.StateObjectRlp, expectedStateDiffBytes) } if !bytes.Equal(expectedReceiptBytes, payload.ReceiptsRlp) { t.Errorf("payload does not have expected receipts\r\nactual receipt rlp: %v\r\nexpected receipt rlp: %v", payload.ReceiptsRlp, expectedReceiptBytes) @@ -175,7 +175,7 @@ func testHTTPAPI(t *testing.T) { BlockHash: block1.Hash(), } expectedReceiptBytes, _ := rlp.EncodeToBytes(types.Receipts{mockReceipt}) - expectedStateDiff := statediff.StateDiff{ + expectedStateDiff := statediff.StateObject{ BlockNumber: block1.Number(), BlockHash: block1.Hash(), Nodes: []statediff.StateNode{ @@ -220,13 +220,13 @@ func testHTTPAPI(t *testing.T) { if err != nil { t.Error(err) } - sort.Slice(payload.StateDiffRlp, func(i, j int) bool { return payload.StateDiffRlp[i] < payload.StateDiffRlp[j] }) + sort.Slice(payload.StateObjectRlp, func(i, j int) bool { return payload.StateObjectRlp[i] < payload.StateObjectRlp[j] }) sort.Slice(expectedStateDiffBytes, func(i, j int) bool { return expectedStateDiffBytes[i] < expectedStateDiffBytes[j] }) if !bytes.Equal(payload.BlockRlp, expectedBlockRlp) { t.Errorf("payload does not have expected block\r\nactual block rlp: %v\r\nexpected block rlp: %v", payload.BlockRlp, expectedBlockRlp) } - if !bytes.Equal(payload.StateDiffRlp, expectedStateDiffBytes) { - t.Errorf("payload does not have expected state diff\r\nactual state diff rlp: %v\r\nexpected state diff rlp: %v", payload.StateDiffRlp, expectedStateDiffBytes) + if !bytes.Equal(payload.StateObjectRlp, expectedStateDiffBytes) { + t.Errorf("payload does not have expected state diff\r\nactual state diff rlp: %v\r\nexpected state diff rlp: %v", payload.StateObjectRlp, expectedStateDiffBytes) } if !bytes.Equal(expectedReceiptBytes, payload.ReceiptsRlp) { t.Errorf("payload does not have expected receipts\r\nactual receipt rlp: %v\r\nexpected receipt rlp: %v", payload.ReceiptsRlp, expectedReceiptBytes) diff --git a/statediff/testhelpers/mocks/builder.go b/statediff/testhelpers/mocks/builder.go index 75af7cf55..5e611aefa 100644 --- a/statediff/testhelpers/mocks/builder.go +++ b/statediff/testhelpers/mocks/builder.go @@ -17,6 +17,7 @@ package mocks import ( + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/statediff" ) @@ -24,20 +25,29 @@ import ( type Builder struct { Args statediff.Args Params statediff.Params - stateDiff statediff.StateDiff + stateDiff statediff.StateObject + block *types.Block + stateTrie statediff.StateObject builderError error } -// BuildStateDiff mock method -func (builder *Builder) BuildStateDiff(args statediff.Args, params statediff.Params) (statediff.StateDiff, 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.StateDiff) { +func (builder *Builder) SetStateDiffToBuild(stateDiff statediff.StateObject) { builder.stateDiff = stateDiff } diff --git a/statediff/types.go b/statediff/types.go index c17684856..c69a2ed4c 100644 --- a/statediff/types.go +++ b/statediff/types.go @@ -56,7 +56,7 @@ type Payload struct { BlockRlp []byte `json:"blockRlp"` TotalDifficulty *big.Int `json:"totalDifficulty"` ReceiptsRlp []byte `json:"receiptsRlp"` - StateDiffRlp []byte `json:"stateDiff" gencodec:"required"` + StateObjectRlp []byte `json:"stateDiff" gencodec:"required"` encoded []byte err error @@ -80,8 +80,8 @@ func (sd *Payload) Encode() ([]byte, error) { return sd.encoded, sd.err } -// StateDiff is the final output structure from the builder -type StateDiff struct { +// StateObject is the final output structure from the builder +type StateObject struct { BlockNumber *big.Int `json:"blockNumber" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"` Nodes []StateNode `json:"Nodes" gencodec:"required"`