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

trie: remove redundant returns + use stacktrie where applicable #22760

Merged
merged 2 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/state/snapshot/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix
}
// Verify the snapshot segment with range prover, ensure that all flat states
// in this range correspond to merkle trie.
_, _, _, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof)
_, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof)
return &proofResult{
keys: keys,
vals: vals,
Expand Down
6 changes: 3 additions & 3 deletions eth/protocols/snap/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2176,7 +2176,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco
if len(keys) > 0 {
end = keys[len(keys)-1]
}
_, _, _, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb)
_, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb)
if err != nil {
logger.Warn("Account range failed proof", "err", err)
// Signal this request as failed, and ready for rescheduling
Expand Down Expand Up @@ -2413,7 +2413,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
if len(nodes) == 0 {
// No proof has been attached, the response must cover the entire key
// space and hash to the origin root.
dbs[i], _, _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil)
dbs[i], _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil)
if err != nil {
s.scheduleRevertStorageRequest(req) // reschedule request
logger.Warn("Storage slots failed proof", "err", err)
Expand All @@ -2428,7 +2428,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
if len(keys) > 0 {
end = keys[len(keys)-1]
}
dbs[i], _, _, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb)
dbs[i], cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb)
if err != nil {
s.scheduleRevertStorageRequest(req) // reschedule request
logger.Warn("Storage range failed proof", "err", err)
Expand Down
14 changes: 1 addition & 13 deletions tests/fuzzers/rangeproof/rangeproof-fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,30 +170,18 @@ func (f *fuzzer) fuzz() int {
}
ok = 1
//nodes, subtrie
nodes, subtrie, notary, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof)
nodes, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof)
if err != nil {
if nodes != nil {
panic("err != nil && nodes != nil")
}
if subtrie != nil {
panic("err != nil && subtrie != nil")
}
if notary != nil {
panic("err != nil && notary != nil")
}
if hasMore {
panic("err != nil && hasMore == true")
}
} else {
if nodes == nil {
panic("err == nil && nodes == nil")
}
if subtrie == nil {
panic("err == nil && subtrie == nil")
}
if notary == nil {
panic("err == nil && subtrie == nil")
}
}
}
return ok
Expand Down
14 changes: 7 additions & 7 deletions trie/notary.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ import (
"github.com/ethereum/go-ethereum/ethdb/memorydb"
)

// KeyValueNotary tracks which keys have been accessed through a key-value reader
// keyValueNotary tracks which keys have been accessed through a key-value reader
// with te scope of verifying if certain proof datasets are maliciously bloated.
type KeyValueNotary struct {
type keyValueNotary struct {
ethdb.KeyValueReader
reads map[string]struct{}
}

// NewKeyValueNotary wraps a key-value database with an access notary to track
// newKeyValueNotary wraps a key-value database with an access notary to track
// which items have bene accessed.
func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary {
return &KeyValueNotary{
func newKeyValueNotary(db ethdb.KeyValueReader) *keyValueNotary {
return &keyValueNotary{
KeyValueReader: db,
reads: make(map[string]struct{}),
}
}

// Get retrieves an item from the underlying database, but also tracks it as an
// accessed slot for bloat checks.
func (k *KeyValueNotary) Get(key []byte) ([]byte, error) {
func (k *keyValueNotary) Get(key []byte) ([]byte, error) {
k.reads[string(key)] = struct{}{}
return k.KeyValueReader.Get(key)
}

// Accessed returns s snapshot of the original key-value store containing only the
// data accessed through the notary.
func (k *KeyValueNotary) Accessed() ethdb.KeyValueStore {
func (k *keyValueNotary) Accessed() ethdb.KeyValueStore {
db := memorydb.New()
for keystr := range k.reads {
key := []byte(keystr)
Expand Down
69 changes: 27 additions & 42 deletions trie/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,115 +464,100 @@ func hasRightElement(node node, key []byte) bool {
//
// Except returning the error to indicate the proof is valid or not, the function will
// also return a flag to indicate whether there exists more accounts/slots in the trie.
func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, *Trie, *KeyValueNotary, bool, error) {
func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, bool, error) {
if len(keys) != len(values) {
return nil, nil, nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
return nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
}
// Ensure the received batch is monotonic increasing.
for i := 0; i < len(keys)-1; i++ {
if bytes.Compare(keys[i], keys[i+1]) >= 0 {
return nil, nil, nil, false, errors.New("range is not monotonically increasing")
return nil, false, errors.New("range is not monotonically increasing")
}
}
// Create a key-value notary to track which items from the given proof the
// range prover actually needed to verify the data
notary := NewKeyValueNotary(proof)
notary := newKeyValueNotary(proof)

// Special case, there is no edge proof at all. The given range is expected
// to be the whole leaf-set in the trie.
if proof == nil {
var (
diskdb = memorydb.New()
triedb = NewDatabase(diskdb)
tr = NewStackTrie(diskdb)
)
tr, err := New(common.Hash{}, triedb)
if err != nil {
return nil, nil, nil, false, err
}
for index, key := range keys {
tr.TryUpdate(key, values[index])
}
if tr.Hash() != rootHash {
return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
}
// Proof seems valid, serialize all the nodes into the database
if _, err := tr.Commit(nil); err != nil {
return nil, nil, nil, false, err
if have, want := tr.Hash(), rootHash; have != want {
return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have)
}
if err := triedb.Commit(rootHash, false, nil); err != nil {
return nil, nil, nil, false, err
// Proof seems valid, serialize remaining nodes into the database
if _, err := tr.Commit(); err != nil {
return nil, false, err
}
return diskdb, tr, notary, false, nil // No more elements
return diskdb, false, nil // No more elements
}
// Special case, there is a provided edge proof but zero key/value
// pairs, ensure there are no more accounts / slots in the trie.
if len(keys) == 0 {
root, val, err := proofToPath(rootHash, nil, firstKey, notary, true)
if err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
if val != nil || hasRightElement(root, firstKey) {
return nil, nil, nil, false, errors.New("more entries available")
return nil, false, errors.New("more entries available")
}
// Since the entire proof is a single path, we can construct a trie and a
// node database directly out of the inputs, no need to generate them
diskdb := notary.Accessed()
tr := &Trie{
db: NewDatabase(diskdb),
root: root,
}
return diskdb, tr, notary, hasRightElement(root, firstKey), nil
return diskdb, hasRightElement(root, firstKey), nil
}
// Special case, there is only one element and two edge keys are same.
// In this case, we can't construct two edge paths. So handle it here.
if len(keys) == 1 && bytes.Equal(firstKey, lastKey) {
root, val, err := proofToPath(rootHash, nil, firstKey, notary, false)
if err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
if !bytes.Equal(firstKey, keys[0]) {
return nil, nil, nil, false, errors.New("correct proof but invalid key")
return nil, false, errors.New("correct proof but invalid key")
}
if !bytes.Equal(val, values[0]) {
return nil, nil, nil, false, errors.New("correct proof but invalid data")
return nil, false, errors.New("correct proof but invalid data")
}
// Since the entire proof is a single path, we can construct a trie and a
// node database directly out of the inputs, no need to generate them
diskdb := notary.Accessed()
tr := &Trie{
db: NewDatabase(diskdb),
root: root,
}
return diskdb, tr, notary, hasRightElement(root, firstKey), nil
return diskdb, hasRightElement(root, firstKey), nil
}
// Ok, in all other cases, we require two edge paths available.
// First check the validity of edge keys.
if bytes.Compare(firstKey, lastKey) >= 0 {
return nil, nil, nil, false, errors.New("invalid edge keys")
return nil, false, errors.New("invalid edge keys")
}
// todo(rjl493456442) different length edge keys should be supported
if len(firstKey) != len(lastKey) {
return nil, nil, nil, false, errors.New("inconsistent edge keys")
return nil, false, errors.New("inconsistent edge keys")
}
// Convert the edge proofs to edge trie paths. Then we can
// have the same tree architecture with the original one.
// For the first edge proof, non-existent proof is allowed.
root, _, err := proofToPath(rootHash, nil, firstKey, notary, true)
if err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
// Pass the root node here, the second path will be merged
// with the first one. For the last edge proof, non-existent
// proof is also allowed.
root, _, err = proofToPath(rootHash, root, lastKey, notary, true)
if err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
// Remove all internal references. All the removed parts should
// be re-filled(or re-constructed) by the given leaves range.
empty, err := unsetInternal(root, firstKey, lastKey)
if err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
// Rebuild the trie with the leaf stream, the shape of trie
// should be same with the original one.
Expand All @@ -588,16 +573,16 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
tr.TryUpdate(key, values[index])
}
if tr.Hash() != rootHash {
return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
}
// Proof seems valid, serialize all the nodes into the database
if _, err := tr.Commit(nil); err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
if err := triedb.Commit(rootHash, false, nil); err != nil {
return nil, nil, nil, false, err
return nil, false, err
}
return diskdb, tr, notary, hasRightElement(root, keys[len(keys)-1]), nil
return diskdb, hasRightElement(root, keys[len(keys)-1]), nil
}

// get returns the child of the given node. Return nil if the
Expand Down
Loading