mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
trie: finish verifyrange based on stacktrie
trie: polishes on range verification
This commit is contained in:
parent
a286868300
commit
14f893fded
3 changed files with 197 additions and 7 deletions
|
|
@ -195,6 +195,10 @@ func TestRangeProof(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
_, err = VerifyRangeProofWithStack(trie.Hash(), keys[0], keys, vals, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +241,10 @@ func TestRangeProofWithNonExistentProof(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
_, err = VerifyRangeProofWithStack(trie.Hash(), first, keys, vals, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -872,16 +880,17 @@ func BenchmarkVerifyRangeProof10(b *testing.B) { benchmarkVerifyRangeProof(b,
|
|||
func BenchmarkVerifyRangeProof100(b *testing.B) { benchmarkVerifyRangeProof(b, 100) }
|
||||
func BenchmarkVerifyRangeProof1000(b *testing.B) { benchmarkVerifyRangeProof(b, 1000) }
|
||||
func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, 5000) }
|
||||
func BenchmarkVerifyRangeProof10K(b *testing.B) { benchmarkVerifyRangeProof(b, 10000) }
|
||||
|
||||
func benchmarkVerifyRangeProof(b *testing.B, size int) {
|
||||
trie, vals := randomTrie(8192)
|
||||
trie, vals := randomTrie(size * 3)
|
||||
var entries []*kv
|
||||
for _, kv := range vals {
|
||||
entries = append(entries, kv)
|
||||
}
|
||||
slices.SortFunc(entries, (*kv).cmp)
|
||||
|
||||
start := 2
|
||||
start := size
|
||||
end := start + size
|
||||
proof := memorydb.New()
|
||||
if err := trie.Prove(entries[start].k, proof); err != nil {
|
||||
|
|
@ -896,8 +905,8 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) {
|
|||
keys = append(keys, entries[i].k)
|
||||
values = append(values, entries[i].v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, proof)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// nodeToStNode converts from `node` to `*stNode`.
|
||||
|
|
@ -222,3 +224,117 @@ func iterateProof(rootHash common.Hash, path []byte, ascending bool, proof ethdb
|
|||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// RootFromLeafs calculates the trie root for the trie built up with the key/values
|
||||
// given as input.
|
||||
// This method errors if
|
||||
// 1. The keys/values are not of equal length
|
||||
// 2. The keys are not monotonically increasing
|
||||
func RootFromLeafs(keys [][]byte, values [][]byte) (common.Hash, error) {
|
||||
var (
|
||||
tr = NewStackTrie(nil)
|
||||
pKey []byte
|
||||
)
|
||||
for i, key := range keys {
|
||||
// Ensure the received batch is monotonic increasing and contains no deletions
|
||||
if bytes.Compare(pKey, key) >= 0 {
|
||||
return common.Hash{}, errors.New("range is not monotonically increasing")
|
||||
}
|
||||
if len(values[i]) == 0 {
|
||||
return common.Hash{}, errors.New("range contains deletion")
|
||||
}
|
||||
tr.Update(key, values[i])
|
||||
pKey = key
|
||||
}
|
||||
return tr.Hash(), nil
|
||||
}
|
||||
|
||||
func VerifyRootFromLeafs(root common.Hash, keys [][]byte, values [][]byte) error {
|
||||
have, err := RootFromLeafs(keys, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if have != root {
|
||||
return fmt.Errorf("want root %x, have %x", root, have)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO @holiman make this handle proofs-of-nonexistence
|
||||
func VerifyRangeProofWithStack(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) {
|
||||
if len(keys) != len(values) {
|
||||
return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
|
||||
}
|
||||
// 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 {
|
||||
return false, VerifyRootFromLeafs(rootHash, keys, values)
|
||||
}
|
||||
// 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, proof, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if val != nil || hasRightElement(root, firstKey) {
|
||||
return false, errors.New("more entries available")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
lastKey := keys[len(keys)-1]
|
||||
// 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, proof, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !bytes.Equal(firstKey, keys[0]) {
|
||||
return false, errors.New("correct proof but invalid key")
|
||||
}
|
||||
if !bytes.Equal(val, values[0]) {
|
||||
return false, errors.New("correct proof but invalid data")
|
||||
}
|
||||
return 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 false, errors.New("invalid edge keys")
|
||||
}
|
||||
// todo(rjl493456442) different length edge keys should be supported
|
||||
if len(firstKey) != len(lastKey) {
|
||||
return false, errors.New("inconsistent edge keys")
|
||||
}
|
||||
// Use the proof to initiate a stacktrie along the first path/value
|
||||
stTrie, err := newStackTrieFromProof(rootHash, firstKey, proof, nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not initate stacktrie: %v", err)
|
||||
}
|
||||
// Feed in the values, starting from 1 (do not re-add the proof-leaf)
|
||||
for i := 1; i < len(keys); i++ {
|
||||
if bytes.Compare(keys[i-1], keys[i]) >= 0 {
|
||||
return false, errors.New("range is not monotonically increasing")
|
||||
}
|
||||
if len(values[i]) == 0 {
|
||||
return false, errors.New("range contains deletion")
|
||||
}
|
||||
stTrie.Update(keys[i], values[i])
|
||||
}
|
||||
// For the right-hand-side, we need a list of hashes ot inject
|
||||
hps, err := iterateProof(rootHash, lastKey, false, proof)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("proof iteration failed: %v", err)
|
||||
}
|
||||
slices.Reverse(hps)
|
||||
// Insert into stacktrie
|
||||
for _, hp := range hps {
|
||||
stTrie.insert(stTrie.root, hp.path, hp.hash[:], nil, newHashed)
|
||||
}
|
||||
if have := stTrie.Hash(); have != rootHash {
|
||||
return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, have)
|
||||
}
|
||||
// hasRightElement is true if the hashes we inserted are non-0
|
||||
return len(hps) > 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package trie
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/exp/slices"
|
||||
|
|
@ -55,8 +57,8 @@ func testStRangeProofLeftside(t *testing.T, trie *Trie, vals map[string]*kv) {
|
|||
t.Fatalf("Failed to prove the first node %v", err)
|
||||
}
|
||||
// Initiate the stacktrie with the proof
|
||||
stTrie, err := newStackTrieFromProof(trie.Hash(), entries[start].k, proof, func(owner common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(haveSponge, owner, path, hash, blob, "path")
|
||||
stTrie, err := newStackTrieFromProof(trie.Hash(), entries[start].k, proof, func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(haveSponge, common.Hash{}, path, hash, blob, "path")
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -67,8 +69,8 @@ func testStRangeProofLeftside(t *testing.T, trie *Trie, vals map[string]*kv) {
|
|||
k, v := common.CopyBytes(entries[i].k), common.CopyBytes(entries[i].v)
|
||||
refTrie.Update(k, v)
|
||||
}
|
||||
refTrie.writeFn = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(wantSponge, owner, path, hash, blob, "path")
|
||||
refTrie.writeFn = func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(wantSponge, common.Hash{}, path, hash, blob, "path")
|
||||
}
|
||||
// Feed the remaining values into them both
|
||||
for i := start + 1; i < len(vals); i++ {
|
||||
|
|
@ -142,3 +144,66 @@ func testStackInsertHash(t *testing.T, trie *Trie, vals map[string]*kv) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackRangeProof(t *testing.T) {
|
||||
trie, vals := randomTrie(4096)
|
||||
var entries []*kv
|
||||
for _, kv := range vals {
|
||||
entries = append(entries, kv)
|
||||
}
|
||||
slices.SortFunc(entries, (*kv).cmp)
|
||||
proof := memorydb.New()
|
||||
entries = entries[1000 : len(entries)-1000] // We snip off 1000 entries on either side
|
||||
// Provide the proof for both first and last entry
|
||||
if err := trie.Prove(entries[0].k, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the first node %v", err)
|
||||
}
|
||||
if err := trie.Prove(entries[len(entries)-1].k, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
testStackRangeProof(t, trie.Hash(), proof, entries)
|
||||
}
|
||||
|
||||
func testStackRangeProof(
|
||||
t *testing.T, rootHash common.Hash,
|
||||
proof ethdb.KeyValueReader, entries []*kv) {
|
||||
|
||||
var leftBorder = keybytesToHex(entries[0].k)
|
||||
var rightBorder = keybytesToHex(entries[len(entries)-1].k)
|
||||
writeFn := func(_ common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
if bytes.HasPrefix(leftBorder, path) {
|
||||
fmt.Printf("path %x tainted left (parent to %x)\n", path, leftBorder)
|
||||
return
|
||||
}
|
||||
if bytes.HasPrefix(rightBorder, path) {
|
||||
fmt.Printf("path %x tainted right (parent to %x)\n", path, rightBorder)
|
||||
return
|
||||
}
|
||||
//fmt.Printf("Committing path %x\n", path)
|
||||
}
|
||||
|
||||
// Use the proof initiate the stacktrie with the first entry
|
||||
stTrie, err := newStackTrieFromProof(rootHash, entries[0].k, proof, writeFn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Feed in the standalone values
|
||||
for i := 1; i < len(entries); i++ {
|
||||
stTrie.Update(entries[i].k, common.CopyBytes(entries[i].v))
|
||||
}
|
||||
// For the right-hand-side, we need a list of hashes ot inject
|
||||
// Obtain the hashes
|
||||
hps, err := iterateProof(rootHash, entries[len(entries)-1].k, false, proof)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
slices.Reverse(hps)
|
||||
// Insert into stacktrie
|
||||
for _, hp := range hps {
|
||||
stTrie.insert(stTrie.root, hp.path, hp.hash[:], nil, newHashed)
|
||||
}
|
||||
have := stTrie.Hash()
|
||||
if have != rootHash {
|
||||
t.Fatalf("have %v want %v", have, rootHash)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue