diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index f54debebed..818f057fe4 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -228,16 +228,8 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ // The snap state is exhausted, pass the entire key/val set for verification root := trieId.Root if origin == nil && !diskMore { - stackTr := trie.NewStackTrie(nil) - for i, key := range keys { - stackTr.Update(key, vals[i]) - } - if gotRoot := stackTr.Hash(); gotRoot != root { - return &proofResult{ - keys: keys, - vals: vals, - proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root), - }, nil + if err := trie.VerifyStandaloneRange(root, keys, vals); err != nil { + return &proofResult{keys: keys, vals: vals, proofErr: err}, nil } return &proofResult{keys: keys, vals: vals}, nil } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 0f5f2ccdfe..050cd8fe1f 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2645,12 +2645,10 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo nodes = append(nodes, node) } } - var err error if len(nodes) == 0 { // No proof has been attached, the response must cover the entire key // space and hash to the origin root. - _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) - if err != nil { + if err := trie.VerifyStandaloneRange(req.roots[i], keys, slots[i]); err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) return err @@ -2658,9 +2656,11 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo } else { // A proof was attached, the response is only partial, check that the // returned data is indeed part of the storage trie - proofdb := nodes.NodeSet() - - var end []byte + var ( + proofdb = nodes.NodeSet() + err error + end []byte + ) if len(keys) > 0 { end = keys[len(keys)-1] } diff --git a/trie/proof.go b/trie/proof.go index a463c80b48..5b16be97ea 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -482,6 +482,11 @@ func hasRightElement(node node, key []byte) bool { // proofs are 'bloated' with neighbour leaves or random data, aside from the 'useful' // data, then the proof will still be accepted. func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) { + if proof == nil { + // Special case, there is no edge proof at all. The given range is expected + // to be the whole leaf-set in the trie. + return false, VerifyStandaloneRange(rootHash, keys, values) + } if len(keys) != len(values) { return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } @@ -496,18 +501,6 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key return false, errors.New("range contains deletion") } } - // 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 { - tr := NewStackTrie(nil) - for index, key := range keys { - tr.Update(key, values[index]) - } - if have, want := tr.Hash(), rootHash; have != want { - return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) - } - return 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 { @@ -579,6 +572,34 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key return hasRightElement(tr.root, keys[len(keys)-1]), nil } +// VerifyStandaloneRange checks whether a trie built with the given leaf nodes +// matches with the specific root. +// The range must be monotonically increasing and contain no deletions. +func VerifyStandaloneRange(rootHash common.Hash, keys [][]byte, values [][]byte) error { + if len(keys) != len(values) { + return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) + } + // Ensure the received batch is monotonic increasing and contains no deletions + for i := 0; i < len(keys)-1; i++ { + if bytes.Compare(keys[i], keys[i+1]) >= 0 { + return errors.New("range is not monotonically increasing") + } + } + for _, value := range values { + if len(value) == 0 { + return errors.New("range contains deletion") + } + } + tr := NewStackTrie(nil) + for index, key := range keys { + tr.Update(key, values[index]) + } + if have, want := tr.Hash(), rootHash; have != want { + return fmt.Errorf("invalid proof, want hash %x, got %x", want, have) + } + return nil // No more elements +} + // get returns the child of the given node. Return nil if the // node with specified key doesn't exist at all. //