* Write state diff to CSV (#2) * port statediff from9b7fd9af80/statediff/statediff.go
; minor fixes * integrating state diff extracting, building, and persisting into geth processes * work towards persisting created statediffs in ipfs; based off github.com/vulcanize/eth-block-extractor * Add a state diff service * Remove diff extractor from blockchain * Update imports * Move statediff on/off check to geth cmd config * Update starting state diff service * Add debugging logs for creating diff * Add statediff extractor and builder tests and small refactoring * Start to write statediff to a CSV * Restructure statediff directory * Pull CSV publishing methods into their own file * Reformatting due to go fmt * Add gomega to vendor dir * Remove testing focuses * Update statediff tests to use golang test pkg instead of ginkgo - builder_test - extractor_test - publisher_test * Use hexutil.Encode instead of deprecated common.ToHex * Remove OldValue from DiffBigInt and DiffUint64 fields * Update builder test * Remove old storage value from updated accounts * Remove old values from created/deleted accounts * Update publisher to account for only storing current account values * Update service loop and fetching previous block * Update testing - remove statediff ginkgo test suite file - move mocks to their own dir * Updates per go fmt * Updates to tests * Pass statediff mode and path in through cli * Return filename from publisher * Remove some duplication in builder * Remove code field from state diff output this is the contract byte code, and it can still be obtained by querying the db by the codeHash * Consolidate acct diff structs for updated & updated/deleted accts * Include block number in csv filename * Clean up error logging * Cleanup formatting, spelling, etc * Address PR comments * Add contract address and storage value to csv * Refactor accumulating account row in csv publisher * Add DiffStorage struct * Add storage key to csv * Address PR comments * Fix publisher to include rows for accounts that don't have store updates * Update builder test after merging in release/1.8 * Update test contract to include storage on contract intialization - so that we're able to test that storage diffing works for created and deleted accounts (not just updated accounts). * Factor out a common trie iterator method in builder * Apply goimports to statediff * Apply gosimple changes to statediff * Gracefully exit geth command(#4) * Statediff for full node (#6) * Open a trie from the in-memory database * Use a node's LeafKey as an identifier instead of the address It was proving difficult to find look the address up from a given path with a full node (sometimes the value wouldn't exist in the disk db). So, instead, for now we are using the node's LeafKey with is a Keccak256 hash of the address, so if we know the address we can figure out which LeafKey it matches up to. * Make sure that statediff has been processed before pruning * Use blockchain stateCache.OpenTrie for storage diffs * Clean up log lines and remove unnecessary fields from builder * Apply go fmt changes * Add a sleep to the blockchain test * refactoring/reorganizing packages * refactoring statediff builder and types and adjusted to relay proofs and paths (still need to make this optional) * refactoring state diff service and adding api which allows for streaming state diff payloads over an rpc websocket subscription * make proofs and paths optional + compress service loop into single for loop (may be missing something here) * option to process intermediate nodes * make state diff rlp serializable * cli parameter to limit statediffing to select account addresses + test * review fixes and fixes for issues ran into in integration * review fixes; proper method signature for api; adjust service so that statediff processing is halted/paused until there is at least one subscriber listening for the results * adjust buffering to improve stability; doc.go; fix notifier err handling * relay receipts with the rest of the data + review fixes/changes * rpc method to get statediff at specific block; requires archival node or the block be within the pruning range * fix linter issues * include total difficulty to the payload * fix state diff builder: emit actual leaf nodes instead of value nodes; diff on the leaf not on the value; emit correct path for intermediate nodes * adjust statediff builder tests to changes and extend to test intermediate nodes; golint * add genesis block to test; handle block 0 in StateDiffAt * rlp files for mainnet blocks 0-3, for tests * builder test on mainnet blocks * common.BytesToHash(path) => crypto.Keaccak256(hash) in builder; BytesToHash produces same hash for e.g. []byte{} and []byte{\x00} - prefix \x00 steps are inconsequential to the hash result * complete tests for early mainnet blocks * diff type for representing deleted accounts * fix builder so that we handle account deletions properly and properly diff storage when an account is moved to a new path; update params * remove cli params; moving them to subscriber defined * remove unneeded bc methods * update service and api; statediffing params are now defined by user through api rather than by service provider by cli * update top level tests * add ability to watch specific storage slots (leaf keys) only * comments; explain logic * update mainnet blocks test * update api_test.go * storage leafkey filter test * cleanup chain maker * adjust chain maker for tests to add an empty account in block1 and switch to EIP-158 afterwards (now we just need to generate enough accounts until one causes the empty account to be touched and removed post-EIP-158 so we can simulate and test that process...); also added 2 new blocks where more contract storage is set and old slots are set to zero so they are removed so we can test that * found an account whose creation causes the empty account to be moved to a new path; this should count as 'touching; the empty account and cause it to be removed according to eip-158... but it doesn't * use new contract in unit tests that has self-destruct ability, so we can test eip-158 since simply moving an account to new path doesn't count as 'touchin' it * handle storage deletions * tests for eip-158 account removal and storage value deletions; there is one edge case left to test where we remove 1 account when only two exist such that the remaining account is moved up and replaces the root branch node * finish testing known edge cases * add endpoint to fetch all state and storage nodes at a given blockheight; useful for generating a recent atate cache/snapshot that we can diff forward from rather than needing to collect all diffs from genesis * test for state trie builder * if statediffing is on, lock tries in triedb until the statediffing service signals they are done using them * fix mock blockchain; golint; bump patch * increase maxRequestContentLength; bump patch * log the sizes of the state objects we are sending * CI build (#20) * CI: run build on PR and on push to master * CI: debug building geth * CI: fix coping file * CI: fix coping file v2 * CI: temporary upload file to release asset * CI: get release upload_url by tag, upload asset to current relase * CI: fix tag name * fix ci build on statediff_at_anyblock-1.9.11 branch * fix publishing assets in release * use context deadline for timeout in eth_call * collect and emit codehash=>code mappings for state objects * subscription endpoint for retrieving all the codehash=>code mappings that exist at provided height * Implement WriteStateDiffAt * Writes state diffs directly to postgres * Adds CLI flags to configure PG * Refactors builder output with callbacks * Copies refactored postgres handling code from ipld-eth-indexer * rename PostgresCIDWriter.{index->upsert}* * go.mod update * rm unused * cleanup * output code & codehash iteratively * had to rf some types for this * prometheus metrics output * duplicate recent eth-indexer changes * migrations and metrics... * [wip] prom.Init() here? another CLI flag? * tidy & DRY * statediff WriteLoop service + CLI flag * [wip] update test mocks * todo - do something meaningful to test write loop * logging * use geth log * port tests to go testing * drop ginkgo/gomega * fix and cleanup tests * fail before defer statement * delete vendor/ dir * fixes after rebase onto 1.9.23 * fix API registration * use golang 1.15.5 version (#34) * bump version meta; add 0.0.11 branch to actions * bump version meta; update github actions workflows * statediff: refactor metrics * Remove redundant statediff/indexer/prom tooling and use existing prometheus integration. * "indexer" namespace for metrics * add reporting loop for db metrics * doc * metrics for statediff stats * metrics namespace/subsystem = statediff/{indexer,service} * statediff: use a worker pool (for direct writes) * fix test * fix chain event subscription * log tweaks * func name * unused import * intermediate chain event channel for metrics * update github actions; linting * add poststate and status to receipt ipld indexes * stateDiffFor endpoints for fetching or writing statediff object by blockhash; bump statediff version * fixes after rebase on to v1.10.1 * update github actions and version meta; go fmt * add leaf key to removed 'nodes' * include Postgres migrations and schema * service documentation * touching up update github actions after rebase fix connection leak (misplaced defer) and perform proper rollback on errs improve error logging; handle PushBlock internal err * build docker image and publish it to Docker Hub on release * add access list tx to unit tests * MarshalBinary and UnmarshalBinary methods for receipt * fix error caused by 2718 by using MarshalBinary instead of EncodeRLP methods * ipld encoding/decoding tests * update TxModel; add AccessListElementModel * index tx type and access lists * add access list metrics * unit tests for tx_type and access list table * unit tests for receipt marshal/unmarshal binary methods * improve documentation of the encoding methods * fix issue identified in linting update github actions and version meta after rebase unit test that fails undeterministically on eip2930 txs, giving same error we are seeing in prod fix bug Include genesis block state diff. Fix linting issue. documentation on versioning, rebasing, releasing; bump version meta Add geth and statediff unit test to CI. Set pgpassword in env. Added comments. Add new major branch to github action. Fix failing test. Fix lint errors. Add support for Dynamic txn(EIP-1559). Update version meta to 0.0.24 Verify block base fee in test. Fix base_fee type and add backward compatible test. Remove type definition for AccessListElementModel Change basefee to int64/bigint. block and uncle reward in PoA network = 0 (#87) * in PoA networks there is no block and uncle rewards * bump meta version (cherry picked from commitb64ca14689
) Use Ropsten to test block reward. Add Makefile target to build static linux binaries. Strip symbol tables from static binaries. Fix block_fee to support NULL values. bump version meta. Add new major branch to github action. Add new major branch to github action. Add new major branch to github action. Add new major branch to github action. rename doc.go to README.md Create a seperate table for storing logs Self review Bump statediff version to 0.0.26. add btree index to state/storage_cids.node_type; updated schema Dedup receipt data. Fix linter errors. Address comments. Bump statediff version to 0.0.27. new cli flag for initializing db first time service is ran only write Removed node ipld block (on db init) and reuse constant cid and mhkey linting test new handling of Removed nodes; don't require init flag log metrics Add new major branch to github action. Fix build. Update golang version in CI. Use ipld-eth-db in testing. Remove migration from repo. Add new major branch to github action. Use `GetTd` instead of `GetTdByHash`6289137827
715 lines
20 KiB
Go
715 lines
20 KiB
Go
// Copyright 2014 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 <http://www.gnu.org/licenses/>.
|
|
|
|
package trie
|
|
|
|
import (
|
|
"bytes"
|
|
"container/heap"
|
|
"errors"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
)
|
|
|
|
// Iterator is a key-value trie iterator that traverses a Trie.
|
|
type Iterator struct {
|
|
nodeIt NodeIterator
|
|
|
|
Key []byte // Current data key on which the iterator is positioned on
|
|
Value []byte // Current data value on which the iterator is positioned on
|
|
Err error
|
|
}
|
|
|
|
// NewIterator creates a new key-value iterator from a node iterator.
|
|
// Note that the value returned by the iterator is raw. If the content is encoded
|
|
// (e.g. storage value is RLP-encoded), it's caller's duty to decode it.
|
|
func NewIterator(it NodeIterator) *Iterator {
|
|
return &Iterator{
|
|
nodeIt: it,
|
|
}
|
|
}
|
|
|
|
// Next moves the iterator forward one key-value entry.
|
|
func (it *Iterator) Next() bool {
|
|
for it.nodeIt.Next(true) {
|
|
if it.nodeIt.Leaf() {
|
|
it.Key = it.nodeIt.LeafKey()
|
|
it.Value = it.nodeIt.LeafBlob()
|
|
return true
|
|
}
|
|
}
|
|
it.Key = nil
|
|
it.Value = nil
|
|
it.Err = it.nodeIt.Error()
|
|
return false
|
|
}
|
|
|
|
// Prove generates the Merkle proof for the leaf node the iterator is currently
|
|
// positioned on.
|
|
func (it *Iterator) Prove() [][]byte {
|
|
return it.nodeIt.LeafProof()
|
|
}
|
|
|
|
// NodeIterator is an iterator to traverse the trie pre-order.
|
|
type NodeIterator interface {
|
|
// Next moves the iterator to the next node. If the parameter is false, any child
|
|
// nodes will be skipped.
|
|
Next(bool) bool
|
|
|
|
// Error returns the error status of the iterator.
|
|
Error() error
|
|
|
|
// Hash returns the hash of the current node.
|
|
Hash() common.Hash
|
|
|
|
// Parent returns the hash of the parent of the current node. The hash may be the one
|
|
// grandparent if the immediate parent is an internal node with no hash.
|
|
Parent() common.Hash
|
|
|
|
// Path returns the hex-encoded path to the current node.
|
|
// Callers must not retain references to the return value after calling Next.
|
|
// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
|
|
Path() []byte
|
|
|
|
// Leaf returns true iff the current node is a leaf node.
|
|
Leaf() bool
|
|
|
|
// LeafKey returns the key of the leaf. The method panics if the iterator is not
|
|
// positioned at a leaf. Callers must not retain references to the value after
|
|
// calling Next.
|
|
LeafKey() []byte
|
|
|
|
// LeafBlob returns the content of the leaf. The method panics if the iterator
|
|
// is not positioned at a leaf. Callers must not retain references to the value
|
|
// after calling Next.
|
|
LeafBlob() []byte
|
|
|
|
// LeafProof returns the Merkle proof of the leaf. The method panics if the
|
|
// iterator is not positioned at a leaf. Callers must not retain references
|
|
// to the value after calling Next.
|
|
LeafProof() [][]byte
|
|
|
|
// AddResolver sets an intermediate database to use for looking up trie nodes
|
|
// before reaching into the real persistent layer.
|
|
//
|
|
// This is not required for normal operation, rather is an optimization for
|
|
// cases where trie nodes can be recovered from some external mechanism without
|
|
// reading from disk. In those cases, this resolver allows short circuiting
|
|
// accesses and returning them from memory.
|
|
//
|
|
// Before adding a similar mechanism to any other place in Geth, consider
|
|
// making trie.Database an interface and wrapping at that level. It's a huge
|
|
// refactor, but it could be worth it if another occurrence arises.
|
|
AddResolver(ethdb.KeyValueStore)
|
|
}
|
|
|
|
// nodeIteratorState represents the iteration state at one particular node of the
|
|
// trie, which can be resumed at a later invocation.
|
|
type nodeIteratorState struct {
|
|
hash common.Hash // Hash of the node being iterated (nil if not standalone)
|
|
node node // Trie node being iterated
|
|
parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
|
|
index int // Child to be processed next
|
|
pathlen int // Length of the path to this node
|
|
}
|
|
|
|
type nodeIterator struct {
|
|
trie *Trie // Trie being iterated
|
|
stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
|
|
path []byte // Path to the current node
|
|
err error // Failure set in case of an internal error in the iterator
|
|
|
|
resolver ethdb.KeyValueStore // Optional intermediate resolver above the disk layer
|
|
}
|
|
|
|
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
|
var errIteratorEnd = errors.New("end of iteration")
|
|
|
|
// seekError is stored in nodeIterator.err if the initial seek has failed.
|
|
type seekError struct {
|
|
key []byte
|
|
err error
|
|
}
|
|
|
|
func (e seekError) Error() string {
|
|
return "seek error: " + e.err.Error()
|
|
}
|
|
|
|
func newNodeIterator(trie *Trie, start []byte) NodeIterator {
|
|
if trie.Hash() == emptyState {
|
|
return new(nodeIterator)
|
|
}
|
|
it := &nodeIterator{trie: trie}
|
|
it.err = it.seek(start)
|
|
return it
|
|
}
|
|
|
|
func (it *nodeIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
|
it.resolver = resolver
|
|
}
|
|
|
|
func (it *nodeIterator) Hash() common.Hash {
|
|
if len(it.stack) == 0 {
|
|
return common.Hash{}
|
|
}
|
|
return it.stack[len(it.stack)-1].hash
|
|
}
|
|
|
|
func (it *nodeIterator) Parent() common.Hash {
|
|
if len(it.stack) == 0 {
|
|
return common.Hash{}
|
|
}
|
|
return it.stack[len(it.stack)-1].parent
|
|
}
|
|
|
|
func (it *nodeIterator) Leaf() bool {
|
|
return hasTerm(it.path)
|
|
}
|
|
|
|
func (it *nodeIterator) LeafKey() []byte {
|
|
if len(it.stack) > 0 {
|
|
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
|
|
return hexToKeyBytes(it.path)
|
|
}
|
|
}
|
|
panic("not at leaf")
|
|
}
|
|
|
|
func (it *nodeIterator) LeafBlob() []byte {
|
|
if len(it.stack) > 0 {
|
|
if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
|
|
return node
|
|
}
|
|
}
|
|
panic("not at leaf")
|
|
}
|
|
|
|
func (it *nodeIterator) LeafProof() [][]byte {
|
|
if len(it.stack) > 0 {
|
|
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
|
|
hasher := newHasher(false)
|
|
defer returnHasherToPool(hasher)
|
|
proofs := make([][]byte, 0, len(it.stack))
|
|
|
|
for i, item := range it.stack[:len(it.stack)-1] {
|
|
// Gather nodes that end up as hash nodes (or the root)
|
|
node, hashed := hasher.proofHash(item.node)
|
|
if _, ok := hashed.(hashNode); ok || i == 0 {
|
|
enc, _ := rlp.EncodeToBytes(node)
|
|
proofs = append(proofs, enc)
|
|
}
|
|
}
|
|
return proofs
|
|
}
|
|
}
|
|
panic("not at leaf")
|
|
}
|
|
|
|
func (it *nodeIterator) Path() []byte {
|
|
return it.path
|
|
}
|
|
|
|
func (it *nodeIterator) Error() error {
|
|
if it.err == errIteratorEnd {
|
|
return nil
|
|
}
|
|
if seek, ok := it.err.(seekError); ok {
|
|
return seek.err
|
|
}
|
|
return it.err
|
|
}
|
|
|
|
// Next moves the iterator to the next node, returning whether there are any
|
|
// further nodes. In case of an internal error this method returns false and
|
|
// sets the Error field to the encountered failure. If `descend` is false,
|
|
// skips iterating over any subnodes of the current node.
|
|
func (it *nodeIterator) Next(descend bool) bool {
|
|
if it.err == errIteratorEnd {
|
|
return false
|
|
}
|
|
if seek, ok := it.err.(seekError); ok {
|
|
if it.err = it.seek(seek.key); it.err != nil {
|
|
return false
|
|
}
|
|
}
|
|
// Otherwise step forward with the iterator and report any errors.
|
|
state, parentIndex, path, err := it.peek(descend)
|
|
it.err = err
|
|
if it.err != nil {
|
|
return false
|
|
}
|
|
it.push(state, parentIndex, path)
|
|
return true
|
|
}
|
|
|
|
func (it *nodeIterator) seek(prefix []byte) error {
|
|
// The path we're looking for is the hex encoded key without terminator.
|
|
key := keybytesToHex(prefix)
|
|
key = key[:len(key)-1]
|
|
// Move forward until we're just before the closest match to key.
|
|
for {
|
|
state, parentIndex, path, err := it.peekSeek(key)
|
|
if err == errIteratorEnd {
|
|
return errIteratorEnd
|
|
} else if err != nil {
|
|
return seekError{prefix, err}
|
|
} else if bytes.Compare(path, key) >= 0 {
|
|
return nil
|
|
}
|
|
it.push(state, parentIndex, path)
|
|
}
|
|
}
|
|
|
|
// init initializes the the iterator.
|
|
func (it *nodeIterator) init() (*nodeIteratorState, error) {
|
|
root := it.trie.Hash()
|
|
state := &nodeIteratorState{node: it.trie.root, index: -1}
|
|
if root != emptyRoot {
|
|
state.hash = root
|
|
}
|
|
return state, state.resolve(it, nil)
|
|
}
|
|
|
|
// peek creates the next state of the iterator.
|
|
func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) {
|
|
// Initialize the iterator if we've just started.
|
|
if len(it.stack) == 0 {
|
|
state, err := it.init()
|
|
return state, nil, nil, err
|
|
}
|
|
if !descend {
|
|
// If we're skipping children, pop the current node first
|
|
it.pop()
|
|
}
|
|
|
|
// Continue iteration to the next child
|
|
for len(it.stack) > 0 {
|
|
parent := it.stack[len(it.stack)-1]
|
|
ancestor := parent.hash
|
|
if (ancestor == common.Hash{}) {
|
|
ancestor = parent.parent
|
|
}
|
|
state, path, ok := it.nextChild(parent, ancestor)
|
|
if ok {
|
|
if err := state.resolve(it, path); err != nil {
|
|
return parent, &parent.index, path, err
|
|
}
|
|
return state, &parent.index, path, nil
|
|
}
|
|
// No more child nodes, move back up.
|
|
it.pop()
|
|
}
|
|
return nil, nil, nil, errIteratorEnd
|
|
}
|
|
|
|
// peekSeek is like peek, but it also tries to skip resolving hashes by skipping
|
|
// over the siblings that do not lead towards the desired seek position.
|
|
func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []byte, error) {
|
|
// Initialize the iterator if we've just started.
|
|
if len(it.stack) == 0 {
|
|
state, err := it.init()
|
|
return state, nil, nil, err
|
|
}
|
|
if !bytes.HasPrefix(seekKey, it.path) {
|
|
// If we're skipping children, pop the current node first
|
|
it.pop()
|
|
}
|
|
|
|
// Continue iteration to the next child
|
|
for len(it.stack) > 0 {
|
|
parent := it.stack[len(it.stack)-1]
|
|
ancestor := parent.hash
|
|
if (ancestor == common.Hash{}) {
|
|
ancestor = parent.parent
|
|
}
|
|
state, path, ok := it.nextChildAt(parent, ancestor, seekKey)
|
|
if ok {
|
|
if err := state.resolve(it, path); err != nil {
|
|
return parent, &parent.index, path, err
|
|
}
|
|
return state, &parent.index, path, nil
|
|
}
|
|
// No more child nodes, move back up.
|
|
it.pop()
|
|
}
|
|
return nil, nil, nil, errIteratorEnd
|
|
}
|
|
|
|
func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
|
|
if it.resolver != nil {
|
|
if blob, err := it.resolver.Get(hash); err == nil && len(blob) > 0 {
|
|
if resolved, err := decodeNode(hash, blob); err == nil {
|
|
return resolved, nil
|
|
}
|
|
}
|
|
}
|
|
resolved, err := it.trie.resolveHash(hash, path)
|
|
return resolved, err
|
|
}
|
|
|
|
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
|
|
if hash, ok := st.node.(hashNode); ok {
|
|
resolved, err := it.resolveHash(hash, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
st.node = resolved
|
|
st.hash = common.BytesToHash(hash)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) {
|
|
var (
|
|
child node
|
|
state *nodeIteratorState
|
|
childPath []byte
|
|
)
|
|
for ; index < len(n.Children); index++ {
|
|
if n.Children[index] != nil {
|
|
child = n.Children[index]
|
|
hash, _ := child.cache()
|
|
state = &nodeIteratorState{
|
|
hash: common.BytesToHash(hash),
|
|
node: child,
|
|
parent: ancestor,
|
|
index: -1,
|
|
pathlen: len(path),
|
|
}
|
|
childPath = append(childPath, path...)
|
|
childPath = append(childPath, byte(index))
|
|
return child, state, childPath, index
|
|
}
|
|
}
|
|
return nil, nil, nil, 0
|
|
}
|
|
|
|
func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) {
|
|
switch node := parent.node.(type) {
|
|
case *fullNode:
|
|
//Full node, move to the first non-nil child.
|
|
if child, state, path, index := findChild(node, parent.index+1, it.path, ancestor); child != nil {
|
|
parent.index = index - 1
|
|
return state, path, true
|
|
}
|
|
case *shortNode:
|
|
// Short node, return the pointer singleton child
|
|
if parent.index < 0 {
|
|
hash, _ := node.Val.cache()
|
|
state := &nodeIteratorState{
|
|
hash: common.BytesToHash(hash),
|
|
node: node.Val,
|
|
parent: ancestor,
|
|
index: -1,
|
|
pathlen: len(it.path),
|
|
}
|
|
path := append(it.path, node.Key...)
|
|
return state, path, true
|
|
}
|
|
}
|
|
return parent, it.path, false
|
|
}
|
|
|
|
// nextChildAt is similar to nextChild, except that it targets a child as close to the
|
|
// target key as possible, thus skipping siblings.
|
|
func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.Hash, key []byte) (*nodeIteratorState, []byte, bool) {
|
|
switch n := parent.node.(type) {
|
|
case *fullNode:
|
|
// Full node, move to the first non-nil child before the desired key position
|
|
child, state, path, index := findChild(n, parent.index+1, it.path, ancestor)
|
|
if child == nil {
|
|
// No more children in this fullnode
|
|
return parent, it.path, false
|
|
}
|
|
// If the child we found is already past the seek position, just return it.
|
|
if bytes.Compare(path, key) >= 0 {
|
|
parent.index = index - 1
|
|
return state, path, true
|
|
}
|
|
// The child is before the seek position. Try advancing
|
|
for {
|
|
nextChild, nextState, nextPath, nextIndex := findChild(n, index+1, it.path, ancestor)
|
|
// If we run out of children, or skipped past the target, return the
|
|
// previous one
|
|
if nextChild == nil || bytes.Compare(nextPath, key) >= 0 {
|
|
parent.index = index - 1
|
|
return state, path, true
|
|
}
|
|
// We found a better child closer to the target
|
|
state, path, index = nextState, nextPath, nextIndex
|
|
}
|
|
case *shortNode:
|
|
// Short node, return the pointer singleton child
|
|
if parent.index < 0 {
|
|
hash, _ := n.Val.cache()
|
|
state := &nodeIteratorState{
|
|
hash: common.BytesToHash(hash),
|
|
node: n.Val,
|
|
parent: ancestor,
|
|
index: -1,
|
|
pathlen: len(it.path),
|
|
}
|
|
path := append(it.path, n.Key...)
|
|
return state, path, true
|
|
}
|
|
}
|
|
return parent, it.path, false
|
|
}
|
|
|
|
func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) {
|
|
it.path = path
|
|
it.stack = append(it.stack, state)
|
|
if parentIndex != nil {
|
|
*parentIndex++
|
|
}
|
|
}
|
|
|
|
func (it *nodeIterator) pop() {
|
|
parent := it.stack[len(it.stack)-1]
|
|
it.path = it.path[:parent.pathlen]
|
|
it.stack = it.stack[:len(it.stack)-1]
|
|
}
|
|
|
|
func compareNodes(a, b NodeIterator) int {
|
|
if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 {
|
|
return cmp
|
|
}
|
|
if a.Leaf() && !b.Leaf() {
|
|
return -1
|
|
} else if b.Leaf() && !a.Leaf() {
|
|
return 1
|
|
}
|
|
if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 {
|
|
return cmp
|
|
}
|
|
if a.Leaf() && b.Leaf() {
|
|
return bytes.Compare(a.LeafBlob(), b.LeafBlob())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type differenceIterator struct {
|
|
a, b NodeIterator // Nodes returned are those in b - a.
|
|
eof bool // Indicates a has run out of elements
|
|
count int // Number of nodes scanned on either trie
|
|
}
|
|
|
|
// NewDifferenceIterator constructs a NodeIterator that iterates over elements in b that
|
|
// are not in a. Returns the iterator, and a pointer to an integer recording the number
|
|
// of nodes seen.
|
|
func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) {
|
|
a.Next(true)
|
|
it := &differenceIterator{
|
|
a: a,
|
|
b: b,
|
|
}
|
|
return it, &it.count
|
|
}
|
|
|
|
func (it *differenceIterator) Hash() common.Hash {
|
|
return it.b.Hash()
|
|
}
|
|
|
|
func (it *differenceIterator) Parent() common.Hash {
|
|
return it.b.Parent()
|
|
}
|
|
|
|
func (it *differenceIterator) Leaf() bool {
|
|
return it.b.Leaf()
|
|
}
|
|
|
|
func (it *differenceIterator) LeafKey() []byte {
|
|
return it.b.LeafKey()
|
|
}
|
|
|
|
func (it *differenceIterator) LeafBlob() []byte {
|
|
return it.b.LeafBlob()
|
|
}
|
|
|
|
func (it *differenceIterator) LeafProof() [][]byte {
|
|
return it.b.LeafProof()
|
|
}
|
|
|
|
func (it *differenceIterator) Path() []byte {
|
|
return it.b.Path()
|
|
}
|
|
|
|
func (it *differenceIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (it *differenceIterator) Next(bool) bool {
|
|
// Invariants:
|
|
// - We always advance at least one element in b.
|
|
// - At the start of this function, a's path is lexically greater than b's.
|
|
if !it.b.Next(true) {
|
|
return false
|
|
}
|
|
it.count++
|
|
|
|
if it.eof {
|
|
// a has reached eof, so we just return all elements from b
|
|
return true
|
|
}
|
|
|
|
for {
|
|
switch compareNodes(it.a, it.b) {
|
|
case -1:
|
|
// b jumped past a; advance a
|
|
if !it.a.Next(true) {
|
|
it.eof = true
|
|
return true
|
|
}
|
|
it.count++
|
|
case 1:
|
|
// b is before a
|
|
return true
|
|
case 0:
|
|
// a and b are identical; skip this whole subtree if the nodes have hashes
|
|
hasHash := it.a.Hash() == common.Hash{}
|
|
if !it.b.Next(hasHash) {
|
|
return false
|
|
}
|
|
it.count++
|
|
if !it.a.Next(hasHash) {
|
|
it.eof = true
|
|
return true
|
|
}
|
|
it.count++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (it *differenceIterator) Error() error {
|
|
if err := it.a.Error(); err != nil {
|
|
return err
|
|
}
|
|
return it.b.Error()
|
|
}
|
|
|
|
type nodeIteratorHeap []NodeIterator
|
|
|
|
func (h nodeIteratorHeap) Len() int { return len(h) }
|
|
func (h nodeIteratorHeap) Less(i, j int) bool { return compareNodes(h[i], h[j]) < 0 }
|
|
func (h nodeIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
|
func (h *nodeIteratorHeap) Push(x interface{}) { *h = append(*h, x.(NodeIterator)) }
|
|
func (h *nodeIteratorHeap) Pop() interface{} {
|
|
n := len(*h)
|
|
x := (*h)[n-1]
|
|
*h = (*h)[0 : n-1]
|
|
return x
|
|
}
|
|
|
|
type unionIterator struct {
|
|
items *nodeIteratorHeap // Nodes returned are the union of the ones in these iterators
|
|
count int // Number of nodes scanned across all tries
|
|
}
|
|
|
|
// NewUnionIterator constructs a NodeIterator that iterates over elements in the union
|
|
// of the provided NodeIterators. Returns the iterator, and a pointer to an integer
|
|
// recording the number of nodes visited.
|
|
func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) {
|
|
h := make(nodeIteratorHeap, len(iters))
|
|
copy(h, iters)
|
|
heap.Init(&h)
|
|
|
|
ui := &unionIterator{items: &h}
|
|
return ui, &ui.count
|
|
}
|
|
|
|
func (it *unionIterator) Hash() common.Hash {
|
|
return (*it.items)[0].Hash()
|
|
}
|
|
|
|
func (it *unionIterator) Parent() common.Hash {
|
|
return (*it.items)[0].Parent()
|
|
}
|
|
|
|
func (it *unionIterator) Leaf() bool {
|
|
return (*it.items)[0].Leaf()
|
|
}
|
|
|
|
func (it *unionIterator) LeafKey() []byte {
|
|
return (*it.items)[0].LeafKey()
|
|
}
|
|
|
|
func (it *unionIterator) LeafBlob() []byte {
|
|
return (*it.items)[0].LeafBlob()
|
|
}
|
|
|
|
func (it *unionIterator) LeafProof() [][]byte {
|
|
return (*it.items)[0].LeafProof()
|
|
}
|
|
|
|
func (it *unionIterator) Path() []byte {
|
|
return (*it.items)[0].Path()
|
|
}
|
|
|
|
func (it *unionIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// Next returns the next node in the union of tries being iterated over.
|
|
//
|
|
// It does this by maintaining a heap of iterators, sorted by the iteration
|
|
// order of their next elements, with one entry for each source trie. Each
|
|
// time Next() is called, it takes the least element from the heap to return,
|
|
// advancing any other iterators that also point to that same element. These
|
|
// iterators are called with descend=false, since we know that any nodes under
|
|
// these nodes will also be duplicates, found in the currently selected iterator.
|
|
// Whenever an iterator is advanced, it is pushed back into the heap if it still
|
|
// has elements remaining.
|
|
//
|
|
// In the case that descend=false - eg, we're asked to ignore all subnodes of the
|
|
// current node - we also advance any iterators in the heap that have the current
|
|
// path as a prefix.
|
|
func (it *unionIterator) Next(descend bool) bool {
|
|
if len(*it.items) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Get the next key from the union
|
|
least := heap.Pop(it.items).(NodeIterator)
|
|
|
|
// Skip over other nodes as long as they're identical, or, if we're not descending, as
|
|
// long as they have the same prefix as the current node.
|
|
for len(*it.items) > 0 && ((!descend && bytes.HasPrefix((*it.items)[0].Path(), least.Path())) || compareNodes(least, (*it.items)[0]) == 0) {
|
|
skipped := heap.Pop(it.items).(NodeIterator)
|
|
// Skip the whole subtree if the nodes have hashes; otherwise just skip this node
|
|
if skipped.Next(skipped.Hash() == common.Hash{}) {
|
|
it.count++
|
|
// If there are more elements, push the iterator back on the heap
|
|
heap.Push(it.items, skipped)
|
|
}
|
|
}
|
|
if least.Next(descend) {
|
|
it.count++
|
|
heap.Push(it.items, least)
|
|
}
|
|
return len(*it.items) > 0
|
|
}
|
|
|
|
func (it *unionIterator) Error() error {
|
|
for i := 0; i < len(*it.items); i++ {
|
|
if err := (*it.items)[i].Error(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|