Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth: add debug_storageRangeAt #14350

Merged
merged 5 commits into from
Apr 25, 2017
Merged

Conversation

fjl
Copy link
Contributor

@fjl fjl commented Apr 19, 2017

The new RPC method allows downloading storage in chunks.

debug_storageRangeAt takes parameters blockHash, txIndex, account, startKey, limit. The startKey applies to the hashed key (instead of the preimage as originally suggested in #3407) . The returned storage values contain the preimage if available. The returned object also contains the nextKey field, which will be non-null if there are more keys after the range that was returned.

Example:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_storageRangeAt",
  "params": [
    "0xced82545131cdb6253f183a62dc3eaf81259be5cc744767798bcfb95664dc32c", // blockHash
    4,                                                                    // txIndex
    "0xB4A5BB81C6962Fa87Af6886ABE59b3cc56ec44b8",                         // account
    "0x",                                                                 // startKey
    2                                                                     // limit
  ]
}
// Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "storage": {
      "0x1ab0c6948a275349ae45a06aad66a8bd65ac18074615d53676c09b67809099e0": {
        "key": "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace",
        "value": "0x00000000000000000000009413739a6ef2d14b5433465dbeffafbc2f2d610551"
      },
      "0x1d3954ea05fac9c4f44f290d32ed11be13922e2c8162b3594bb3bb0405546e8e": {
        "key": "0x2a359015afbfbe3b36db751c88f03aed63209927e8cf9ca7f0b69158e29e28f5",
        "value": "0x0000000000000000000000000000000000000000000000000000000000000004"
      }
    },
    "nextKey": "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"
  }
}

// Continuing the download using nextKey as startKey:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "debug_storageRangeAt",
  "params": [
    "0xced82545131cdb6253f183a62dc3eaf81259be5cc744767798bcfb95664dc32c",
    4,
    "0xB4A5BB81C6962Fa87Af6886ABE59b3cc56ec44b8",
    "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563",
    100
  ]
}
// The response has nextKey == null because there aren't any more values:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "storage": {
      "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": {
        "key": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "value": "0x0000000000000000000000945e237bf1540eea146f5e830593d1d54c279616a9"
      },
      ... // 16 more entries elided
    },
    "nextKey": null
  }
}

We agreed on a preliminary spec in #3407. This supersedes that PR.
The earlier commits leading up to the new API add support for seeking in the trie iterator.

The key was constructed from nibbles, which isn't possible for all
nodes. Remove the only use of Key in LightTrie by always retrying with
the original key that was looked up.
@fjl fjl requested a review from Arachnid April 19, 2017 10:13
@fjl fjl force-pushed the trie-iterator-skip-2 branch 2 times, most recently from 5fb8474 to e1678ce Compare April 19, 2017 13:23
// in the case of an odd number. All remaining nibbles (now an even number) fit properly
// into the remaining bytes. Compact encoding is used for nodes stored on disk.

func hexToCompact(hex []byte) []byte {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you define typedefs for hex and compact encoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because both types of key can be stored in shortNode.Key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could always use casts there. Typedefs would be helpful to avoid confusion elsewhere - though the function naming accomplishes some of that at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to change it in this PR. We can do it in another PR.

trie/iterator.go Outdated
}

// NewNodeIterator creates an post-order trie iterator.
func NewNodeIterator(trie *Trie) NodeIterator {
// newNodeIterator creates an post-order trie iterator.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preorder. :)

trie/iterator.go Outdated
// NewNodeIterator creates an post-order trie iterator.
func NewNodeIterator(trie *Trie) NodeIterator {
// newNodeIterator creates an post-order trie iterator.
func newNodeIterator(trie *Trie, start []byte) NodeIterator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the motivation behind making this internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation was to have a single constructor for each iterator. Please see the commit message.

trie/iterator.go Outdated
for parent.child++; parent.child < len(node.Children); parent.child++ {
child := node.Children[parent.child]
// Full node, move to the first non-nil child.
i := parent.index + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this part of the loop initializer any longer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accident

if err := checkIteratorOrder(testdata1[1:], it); err != nil {
t.Fatal(err)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for seeking to the empty string, and for seeking to a key past the end?

Copy link
Contributor Author

@fjl fjl Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to test empty string, all other tests use NodeIterator(nil) ;). I've added one that seeks past the end.

@fjl fjl force-pushed the trie-iterator-skip-2 branch from 69e1404 to 2f13ce1 Compare April 19, 2017 22:02
eth/api_test.go Outdated
state, _ = state.New(common.Hash{}, db)
addr = common.Address{0x01}
keys = []common.Hash{
common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume these are the hashes of the keys in the array below? Could you add a note to that effect for anyone coming across this later?

@fjl fjl force-pushed the trie-iterator-skip-2 branch from 43de846 to b22dfc8 Compare April 20, 2017 10:23
@karalabe karalabe added this to the 1.6.1 milestone Apr 20, 2017
Copy link
Member

@karalabe karalabe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor issues mostly. Please check and consider whether to fix or not.

One potentially bigger issue I saw was in the state object deep copy. Please check hat part out in detail and explain why it works if it works.

}

// Continue iteration to the next child
outer:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ I hate these labels so much :P

node, err := it.trie.resolveHash(hash, nil, nil)
if err != nil {
return err
return it.stack[len(it.stack)-1], &parent.index, it.path, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why return the previous "item" if peeking at the next one fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to return something. The idea was that the iterator wouldn't advance if there was an error fetching the next item.

trie/encoding.go Outdated
// KEYBYTES encoding contains the actual key and nothing else. This encoding is the
// input to most API functions.
//
// HEX encoding contains on byte for each nibble of the key and an optional trailing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onE byte

// the second-lowest encoding whether the node at the key is a value node. The low nibble
// of the first byte is zero in the case of an even number of nibbles and the first nibble
// in the case of an odd number. All remaining nibbles (now an even number) fit properly
// into the remaining bytes. Compact encoding is used for nodes stored on disk.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sweet, I've never seen this part of the code in 2 years :))

key: []byte{0x12, 0x34, 0x56},
hexIn: []byte{1, 2, 3, 4, 5, 6},
hexOut: []byte{1, 2, 3, 4, 5, 6, 16},
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for encoding/decoding empty keys? The previous code had explicit checks whereas the new code does this implicitly by relying on the calculation returning an empty slice. There's even a slight API change, where the old code returned nil in hexToKeybytes whereas the new returns []byte{}. I'd be happy to see an explicit test that verifies this weird case too.

{hex: []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x20, 0x0f, 0x1c, 0xb8}},
// odd length, terminator
{hex: []byte{15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x3f, 0x1c, 0xb8}},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for encoding/decoding empty keys?

if self.trie != nil {
// A shallow copy makes the two tries independent.
cpy := *self.trie
stateObject.trie = &cpy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't stateObject.trie a trie.SecureTrie? Because that one is a much fancier construct that won't "deep copy" so easily https://github.com/ethereum/go-ethereum/blob/master/trie/secure_trie.go#L44

Copy link
Contributor Author

@fjl fjl Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shallow copy support for SecureTrie was added in 710435b51, by yours truly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't trust that guy.... he looks shady af ;)

@@ -296,6 +296,17 @@ func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash {
return common.Hash{}
}

// StorageTrie returns an iterator over storage trie of an account.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like an iterator to me :P

}, nil
case *ethapi.JavascriptTracer:
return tracer.GetResult()
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this ever happen? If the tx is already in our chain, I think it's safe to say this won't ever fail. (Mentioning because you deleted a similar check at tx.AsMessage).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The around AsMessage is for deriving the sender. I don't want to remove this one because many more things can go wrong (including DB errors, etc.).

fjl added 4 commits April 25, 2017 02:14
'encode' and 'decode' are meaningless because the code deals with three
encodings. Document the encodings and give a name to each one.
Make it so each iterator has exactly one public constructor:

- NodeIterators can be created through a method.
- Iterators can be created through NewIterator on any NodeIterator.
The 'step' method is split into two parts, 'peek' and 'push'. peek
returns the next state but doesn't make it current.

The end of iteration was previously tracked by setting 'trie' to nil.
End of iteration is now tracked using the 'iteratorEnd' error, which is
slightly cleaner and requires less code.
@fjl fjl force-pushed the trie-iterator-skip-2 branch from b22dfc8 to 207bd7d Compare April 25, 2017 00:14
Copy link
Member

@karalabe karalabe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants