les, light, trie: iterator based proofs, no rlp types

This commit is contained in:
Péter Szilágyi 2017-09-21 17:26:49 +03:00
parent 35767dfd0c
commit 486e795d1b
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
10 changed files with 167 additions and 70 deletions

View file

@ -710,7 +710,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// A batch of merkle proofs arrived to one of our previous requests // A batch of merkle proofs arrived to one of our previous requests
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
Data [][]rlp.RawValue Data [][][]byte
} }
if err := msg.Decode(&resp); err != nil { if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -316,7 +315,7 @@ func testGetProofs(t *testing.T, protocol int) {
defer peer.close() defer peer.close()
var proofreqs []ProofReq var proofreqs []ProofReq
var proofs [][]rlp.RawValue var proofs [][][]byte
accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}} accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}}
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {

View file

@ -215,7 +215,7 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
if msg.MsgType != MsgProofs { if msg.MsgType != MsgProofs {
return errInvalidMessageType return errInvalidMessageType
} }
proofs := msg.Obj.([][]rlp.RawValue) proofs := msg.Obj.([][][]byte)
if len(proofs) != 1 { if len(proofs) != 1 {
return errMultipleEntries return errMultipleEntries
} }
@ -286,7 +286,7 @@ type ChtReq struct {
type ChtResp struct { type ChtResp struct {
Header *types.Header Header *types.Header
Proof []rlp.RawValue Proof [][]byte
} }
// ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface // ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface

View file

@ -168,4 +168,4 @@ type CodeData []struct {
Value []byte Value []byte
} }
type proofsData [][]rlp.RawValue type proofsData [][][]byte

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
) )
// NoOdr is the default context passed to an ODR capable function when the ODR // NoOdr is the default context passed to an ODR capable function when the ODR
@ -80,7 +79,7 @@ type TrieRequest struct {
OdrRequest OdrRequest
Id *TrieID Id *TrieID
Key []byte Key []byte
Proof []rlp.RawValue Proof [][]byte
} }
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database
@ -89,7 +88,7 @@ func (req *TrieRequest) StoreResult(db ethdb.Database) {
} }
// storeProof stores the new trie nodes obtained from a merkle proof in the database // storeProof stores the new trie nodes obtained from a merkle proof in the database
func storeProof(db ethdb.Database, proof []rlp.RawValue) { func storeProof(db ethdb.Database, proof [][]byte) {
for _, buf := range proof { for _, buf := range proof {
hash := crypto.Keccak256(buf) hash := crypto.Keccak256(buf)
val, _ := db.Get(hash) val, _ := db.Get(hash)
@ -145,7 +144,7 @@ type ChtRequest struct {
ChtRoot common.Hash ChtRoot common.Hash
Header *types.Header Header *types.Header
Td *big.Int Td *big.Int
Proof []rlp.RawValue Proof [][]byte
} }
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database

View file

@ -22,6 +22,7 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
// Iterator is a key-value trie iterator that traverses a Trie. // Iterator is a key-value trie iterator that traverses a Trie.
@ -55,31 +56,50 @@ func (it *Iterator) Next() bool {
return false 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. // NodeIterator is an iterator to traverse the trie pre-order.
type NodeIterator interface { type NodeIterator interface {
// Next moves the iterator to the next node. If the parameter is false, any child // Next moves the iterator to the next node. If the parameter is false, any child
// nodes will be skipped. // nodes will be skipped.
Next(bool) bool Next(bool) bool
// Error returns the error status of the iterator. // Error returns the error status of the iterator.
Error() error Error() error
// Hash returns the hash of the current node. // Hash returns the hash of the current node.
Hash() common.Hash Hash() common.Hash
// Parent returns the hash of the parent of the current node. The hash may be the one // 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. // grandparent if the immediate parent is an internal node with no hash.
Parent() common.Hash Parent() common.Hash
// Path returns the hex-encoded path to the current node. // Path returns the hex-encoded path to the current node.
// Callers must not retain references to the return value after calling Next. // 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. // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
Path() []byte Path() []byte
// Leaf returns true iff the current node is a leaf node. // Leaf returns true iff the current node is a leaf node.
// LeafBlob, LeafKey return the contents and key of the leaf node. These
// method panic if the iterator is not positioned at a leaf.
// Callers must not retain references to their return value after calling Next
Leaf() bool Leaf() bool
LeafBlob() []byte
// 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 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
} }
// nodeIteratorState represents the iteration state at one particular node of the // nodeIteratorState represents the iteration state at one particular node of the
@ -139,6 +159,15 @@ func (it *nodeIterator) Leaf() bool {
return hasTerm(it.path) 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 { func (it *nodeIterator) LeafBlob() []byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
@ -148,10 +177,22 @@ func (it *nodeIterator) LeafBlob() []byte {
panic("not at leaf") panic("not at leaf")
} }
func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafProof() [][]byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
return hexToKeybytes(it.path) hasher := newHasher(0, 0)
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, _, _ := hasher.hashChildren(item.node, nil)
hashed, _ := hasher.store(node, nil, false)
if _, ok := hashed.(hashNode); ok || i == 0 {
enc, _ := rlp.EncodeToBytes(node)
proofs = append(proofs, enc)
}
}
return proofs
} }
} }
panic("not at leaf") panic("not at leaf")
@ -361,12 +402,16 @@ func (it *differenceIterator) Leaf() bool {
return it.b.Leaf() return it.b.Leaf()
} }
func (it *differenceIterator) LeafKey() []byte {
return it.b.LeafKey()
}
func (it *differenceIterator) LeafBlob() []byte { func (it *differenceIterator) LeafBlob() []byte {
return it.b.LeafBlob() return it.b.LeafBlob()
} }
func (it *differenceIterator) LeafKey() []byte { func (it *differenceIterator) LeafProof() [][]byte {
return it.b.LeafKey() return it.b.LeafProof()
} }
func (it *differenceIterator) Path() []byte { func (it *differenceIterator) Path() []byte {
@ -464,12 +509,16 @@ func (it *unionIterator) Leaf() bool {
return (*it.items)[0].Leaf() return (*it.items)[0].Leaf()
} }
func (it *unionIterator) LeafKey() []byte {
return (*it.items)[0].LeafKey()
}
func (it *unionIterator) LeafBlob() []byte { func (it *unionIterator) LeafBlob() []byte {
return (*it.items)[0].LeafBlob() return (*it.items)[0].LeafBlob()
} }
func (it *unionIterator) LeafKey() []byte { func (it *unionIterator) LeafProof() [][]byte {
return (*it.items)[0].LeafKey() return (*it.items)[0].LeafProof()
} }
func (it *unionIterator) Path() []byte { func (it *unionIterator) Path() []byte {

View file

@ -36,7 +36,7 @@ import (
// contains all nodes of the longest existing prefix of the key // contains all nodes of the longest existing prefix of the key
// (at least the root node), ending with the node that proves the // (at least the root node), ending with the node that proves the
// absence of the key. // absence of the key.
func (t *Trie) Prove(key []byte) []rlp.RawValue { func (t *Trie) Prove(key []byte) [][]byte {
// Collect all nodes on the path to key. // Collect all nodes on the path to key.
key = keybytesToHex(key) key = keybytesToHex(key)
nodes := []node{} nodes := []node{}
@ -68,7 +68,7 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
} }
} }
hasher := newHasher(0, 0) hasher := newHasher(0, 0)
proof := make([]rlp.RawValue, 0, len(nodes)) proof := make([][]byte, 0, len(nodes))
for i, n := range nodes { for i, n := range nodes {
// Don't bother checking for errors here since hasher panics // Don't bother checking for errors here since hasher panics
// if encoding doesn't work and we're not writing to any database. // if encoding doesn't work and we're not writing to any database.
@ -88,7 +88,7 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
// value for key in a trie with the given root hash. VerifyProof // value for key in a trie with the given root hash. VerifyProof
// returns an error if the proof contains invalid trie nodes or the // returns an error if the proof contains invalid trie nodes or the
// wrong value. // wrong value.
func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value []byte, err error) { func VerifyProof(rootHash common.Hash, key []byte, proof [][]byte) (value []byte, err error) {
key = keybytesToHex(key) key = keybytesToHex(key)
sha := sha3.NewKeccak256() sha := sha3.NewKeccak256()
wantHash := rootHash.Bytes() wantHash := rootHash.Bytes()
@ -107,10 +107,8 @@ func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value
case nil: case nil:
if i != len(proof)-1 { if i != len(proof)-1 {
return nil, fmt.Errorf("key mismatch at proof node %d", i) return nil, fmt.Errorf("key mismatch at proof node %d", i)
} else {
// The trie doesn't contain the key.
return nil, nil
} }
return nil, nil // The trie doesn't contain the key.
case hashNode: case hashNode:
key = keyrest key = keyrest
wantHash = cld wantHash = cld

View file

@ -24,27 +24,47 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
func init() { func init() {
mrand.Seed(time.Now().Unix()) mrand.Seed(time.Now().Unix())
} }
// makeProvers creates Merkle trie provers based on different implementations to
// test all variations.
func makeProvers(trie *Trie) []func(key []byte) [][]byte {
var provers []func(key []byte) [][]byte
// Create a direct trie based Merkle prover
provers = append(provers, func(key []byte) [][]byte {
return trie.Prove(key)
})
// Create a leaf iterator based Merkle prover
provers = append(provers, func(key []byte) [][]byte {
if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) {
return it.Prove()
}
return nil
})
return provers
}
func TestProof(t *testing.T) { func TestProof(t *testing.T) {
trie, vals := randomTrie(500) trie, vals := randomTrie(500)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proof := trie.Prove(kv.k) for _, kv := range vals {
if proof == nil { proof := prover(kv.k)
t.Fatalf("missing key %x while constructing proof", kv.k) if proof == nil {
} t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k)
val, err := VerifyProof(root, kv.k, proof) }
if err != nil { val, err := VerifyProof(root, kv.k, proof)
t.Fatalf("VerifyProof error for key %x: %v\nraw proof: %x", kv.k, err, proof) if err != nil {
} t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof)
if !bytes.Equal(val, kv.v) { }
t.Fatalf("VerifyProof returned wrong value for key %x: got %x, want %x", kv.k, val, kv.v) if !bytes.Equal(val, kv.v) {
t.Fatalf("prover %d: verified valuemismatch for key %x: have %x, want %x", i, kv.k, val, kv.v)
}
} }
} }
} }
@ -52,33 +72,61 @@ func TestProof(t *testing.T) {
func TestOneElementProof(t *testing.T) { func TestOneElementProof(t *testing.T) {
trie := new(Trie) trie := new(Trie)
updateString(trie, "k", "v") updateString(trie, "k", "v")
proof := trie.Prove([]byte("k")) for i, prover := range makeProvers(trie) {
if proof == nil { proof := prover([]byte("k"))
t.Fatal("nil proof") if proof == nil {
} t.Fatalf("prover %d: nil proof", i)
if len(proof) != 1 { }
t.Error("proof should have one element") if len(proof) != 1 {
} t.Errorf("prover %d: proof should have one element", i)
val, err := VerifyProof(trie.Hash(), []byte("k"), proof) }
if err != nil { val, err := VerifyProof(trie.Hash(), []byte("k"), proof)
t.Fatalf("VerifyProof error: %v\nraw proof: %x", err, proof) if err != nil {
} t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
if !bytes.Equal(val, []byte("v")) { }
t.Fatalf("VerifyProof returned wrong value: got %x, want 'k'", val) if !bytes.Equal(val, []byte("v")) {
t.Fatalf("prover %d: verified valuemismatch: have %x, want 'k'", i, val)
}
} }
} }
func TestVerifyBadProof(t *testing.T) { func TestBadProof(t *testing.T) {
trie, vals := randomTrie(800) trie, vals := randomTrie(800)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proof := trie.Prove(kv.k) for _, kv := range vals {
if proof == nil { proof := prover(kv.k)
t.Fatal("nil proof") if proof == nil {
t.Fatalf("prover %d: nil proof", i)
}
mutateByte(proof[mrand.Intn(len(proof))])
if _, err := VerifyProof(root, kv.k, proof); err == nil {
t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k)
}
} }
mutateByte(proof[mrand.Intn(len(proof))]) }
if _, err := VerifyProof(root, kv.k, proof); err == nil { }
t.Fatalf("expected proof to fail for key %x", kv.k)
// Tests that missing keys can also be proven. The test explicitly uses a single
// entry trie and checks for missing keys both before and after the single entry.
func TestMissingKeyProof(t *testing.T) {
trie := new(Trie)
updateString(trie, "k", "v")
for i, key := range []string{"a", "j", "l", "z"} {
proof := trie.Prove([]byte(key))
if proof == nil {
t.Fatalf("test %d: nil proof", i)
}
if len(proof) != 1 {
t.Errorf("test %d: proof should have one element", i)
}
val, err := VerifyProof(trie.Hash(), []byte(key), proof)
if err != nil {
t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
}
if val != nil {
t.Fatalf("test %d: verified valuemismatch: have %x, want nil", i, val)
} }
} }
} }
@ -114,7 +162,7 @@ func BenchmarkVerifyProof(b *testing.B) {
trie, vals := randomTrie(100) trie, vals := randomTrie(100)
root := trie.Hash() root := trie.Hash()
var keys []string var keys []string
var proofs [][]rlp.RawValue var proofs [][][]byte
for k := range vals { for k := range vals {
keys = append(keys, k) keys = append(keys, k)
proofs = append(proofs, trie.Prove([]byte(k))) proofs = append(proofs, trie.Prove([]byte(k)))

View file

@ -45,11 +45,15 @@ type request struct {
callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch
} }
// SyncResult is a simple list to return missing nodes along with their request // SyncResult represents a response to a trie node retrieval request. The result
// hashes. // data might be a simple binary blob if returning only a single node, or it may
// be a batch of trie leaves (with associated merkle proofs) if returning batched
// results.
type SyncResult struct { type SyncResult struct {
Hash common.Hash // Hash of the originally unknown trie node Hash common.Hash // Hash of the originally unknown trie node
Data []byte // Data content of the retrieved node Data []byte // Data content of the retrieved node, in node-sync mode
Leaves [][]byte // Trie leaves rooted under the specified hash, in leaf-sync mode
Proofs [][]byte // Proofs to validate the leaves, in leaf-sync mode, if leaves are partial
} }
// syncMemBatch is an in-memory buffer of successfully downloaded but not yet // syncMemBatch is an in-memory buffer of successfully downloaded but not yet

View file

@ -120,7 +120,7 @@ func testIterativeTrieSync(t *testing.T, batch int) {
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
results[i] = SyncResult{hash, data} results[i] = SyncResult{Hash: hash, Data: data}
} }
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
@ -153,7 +153,7 @@ func TestIterativeDelayedTrieSync(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
results[i] = SyncResult{hash, data} results[i] = SyncResult{Hash: hash, Data: data}
} }
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
@ -193,7 +193,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
results = append(results, SyncResult{hash, data}) results = append(results, SyncResult{Hash: hash, Data: data})
} }
// Feed the retrieved results back and queue new tasks // Feed the retrieved results back and queue new tasks
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
@ -233,7 +233,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
results = append(results, SyncResult{hash, data}) results = append(results, SyncResult{Hash: hash, Data: data})
if len(results) >= cap(results) { if len(results) >= cap(results) {
break break
@ -282,7 +282,7 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
} }
requested[hash] = struct{}{} requested[hash] = struct{}{}
results[i] = SyncResult{hash, data} results[i] = SyncResult{Hash: hash, Data: data}
} }
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
@ -316,7 +316,7 @@ func TestIncompleteTrieSync(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
results[i] = SyncResult{hash, data} results[i] = SyncResult{Hash: hash, Data: data}
} }
// Process each of the trie nodes // Process each of the trie nodes
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {