Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.

Commit 32ba9c3

Browse files
committed
eth/protocols/snap: fix snap sync failure on empty storage range
commit ethereum/go-ethereum@1cb3b6a In PR ethereum/go-ethereum@39fb82b, we ensure that if accumulated storage response exceeds hard limit cap (and being cutdown in size), the proof will not be attached, ensuring that the prover will not mislead more entries to requested. However, we now mishandle the normal scenario when the particular range requested does not contain any slots. This PR aims to fix this.
1 parent 3cb30d9 commit 32ba9c3

File tree

3 files changed

+154
-22
lines changed

3 files changed

+154
-22
lines changed

eth/protocols/snap/sync.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -2575,7 +2575,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
25752575
// the requested data. For storage range queries that means the state being
25762576
// retrieved was either already pruned remotely, or the peer is not yet
25772577
// synced to our head.
2578-
if len(hashes) == 0 {
2578+
if len(hashes) == 0 && len(proof) == 0 {
25792579
logger.Debug("Peer rejected storage request")
25802580
s.statelessPeers[peer.ID()] = struct{}{}
25812581
s.lock.Unlock()
@@ -2587,6 +2587,14 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
25872587
// Reconstruct the partial tries from the response and verify them
25882588
var cont bool
25892589

2590+
// If a proof was attached while the response is empty, it indicates that the
2591+
// requested range specified with 'origin' is empty. Construct an empty state
2592+
// response locally to finalize the range.
2593+
if len(hashes) == 0 && len(proof) > 0 {
2594+
hashes = append(hashes, []common.Hash{})
2595+
slots = append(slots, [][]byte{})
2596+
}
2597+
25902598
for i := 0; i < len(hashes); i++ {
25912599
// Convert the keys and proofs into an internal format
25922600
keys := make([][]byte, len(hashes[i]))

eth/protocols/snap/sync_test.go

+92-21
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ import (
2727
"testing"
2828
"time"
2929

30+
mrand "math/rand"
31+
3032
"github.com/ethereum/go-ethereum/common"
3133
"github.com/ethereum/go-ethereum/core/rawdb"
3234
"github.com/ethereum/go-ethereum/core/types"
3335
"github.com/ethereum/go-ethereum/crypto"
3436
"github.com/ethereum/go-ethereum/ethdb"
37+
"github.com/ethereum/go-ethereum/internal/testrand"
3538
"github.com/ethereum/go-ethereum/light"
3639
"github.com/ethereum/go-ethereum/log"
3740
"github.com/ethereum/go-ethereum/rlp"
@@ -729,7 +732,7 @@ func TestSyncWithStorage(t *testing.T) {
729732
})
730733
}
731734
)
732-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false)
735+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false, false)
733736

734737
mkSource := func(name string) *testPeer {
735738
source := newTestPeer(name, t, term)
@@ -761,7 +764,7 @@ func TestMultiSyncManyUseless(t *testing.T) {
761764
})
762765
}
763766
)
764-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
767+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false, false)
765768

766769
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
767770
source := newTestPeer(name, t, term)
@@ -807,7 +810,7 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) {
807810
})
808811
}
809812
)
810-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
813+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false, false)
811814

812815
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
813816
source := newTestPeer(name, t, term)
@@ -858,7 +861,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) {
858861
})
859862
}
860863
)
861-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
864+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false, false)
862865

863866
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
864867
source := newTestPeer(name, t, term)
@@ -1125,7 +1128,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) {
11251128
})
11261129
}
11271130
)
1128-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true)
1131+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true, false)
11291132

11301133
mkSource := func(name string) *testPeer {
11311134
source := newTestPeer(name, t, term)
@@ -1161,7 +1164,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) {
11611164
})
11621165
}
11631166
)
1164-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false)
1167+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false, false)
11651168

11661169
mkSource := func(name string, slow bool) *testPeer {
11671170
source := newTestPeer(name, t, term)
@@ -1202,7 +1205,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) {
12021205
})
12031206
}
12041207
)
1205-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
1208+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false, false)
12061209

12071210
mkSource := func(name string, handler storageHandlerFunc) *testPeer {
12081211
source := newTestPeer(name, t, term)
@@ -1240,7 +1243,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) {
12401243
})
12411244
}
12421245
)
1243-
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
1246+
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false, false)
12441247

12451248
mkSource := func(name string, handler storageHandlerFunc) *testPeer {
12461249
source := newTestPeer(name, t, term)
@@ -1298,6 +1301,37 @@ func TestSyncWithStorageMisbehavingProve(t *testing.T) {
12981301
verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
12991302
}
13001303

1304+
func TestSyncWithUnevenStorage(t *testing.T) {
1305+
t.Parallel()
1306+
var (
1307+
once sync.Once
1308+
cancel = make(chan struct{})
1309+
term = func() {
1310+
once.Do(func() {
1311+
close(cancel)
1312+
})
1313+
}
1314+
)
1315+
1316+
accountTrie, accounts, storageTries, storageElems := makeAccountTrieWithStorage(3, 256, false, false, true)
1317+
mkSource := func(name string) *testPeer {
1318+
source := newTestPeer(name, t, term)
1319+
source.accountTrie = accountTrie
1320+
source.accountValues = accounts
1321+
source.storageTries = storageTries
1322+
source.storageValues = storageElems
1323+
source.storageRequestHandler = func(t *testPeer, reqId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
1324+
return defaultStorageRequestHandler(t, reqId, root, accounts, origin, limit, 128) // retrieve storage in large mode
1325+
}
1326+
return source
1327+
}
1328+
syncer := setupSyncer(mkSource("source"))
1329+
if err := syncer.Sync(accountTrie.Hash(), cancel); err != nil {
1330+
t.Fatalf("sync failed: %v", err)
1331+
}
1332+
verifyTrie(syncer.db, accountTrie.Hash(), t)
1333+
}
1334+
13011335
type kv struct {
13021336
k, v []byte
13031337
}
@@ -1466,33 +1500,37 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool)
14661500
}
14671501

14681502
// makeAccountTrieWithStorage spits out a trie, along with the leafs
1469-
func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
1503+
func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool, uneven bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
14701504
var (
14711505
db = trie.NewDatabase(rawdb.NewMemoryDatabase())
14721506
accTrie, _ = trie.New(common.Hash{}, db)
14731507
entries entrySlice
14741508
storageTries = make(map[common.Hash]*trie.Trie)
14751509
storageEntries = make(map[common.Hash]entrySlice)
14761510
)
1477-
// Make a storage trie which we reuse for the whole lot
1478-
var (
1479-
stTrie *trie.Trie
1480-
stEntries entrySlice
1481-
)
1482-
if boundary {
1483-
stTrie, stEntries = makeBoundaryStorageTrie(slots, db)
1484-
} else {
1485-
stTrie, stEntries = makeStorageTrieWithSeed(uint64(slots), 0, db)
1486-
}
1487-
stRoot := stTrie.Hash()
14881511

14891512
// Create n accounts in the trie
14901513
for i := uint64(1); i <= uint64(accounts); i++ {
1514+
var (
1515+
stTrie *trie.Trie
1516+
stEntries entrySlice
1517+
)
1518+
14911519
key := key32(i)
14921520
codehash := emptyCode[:]
14931521
if code {
14941522
codehash = getCodeHash(i)
14951523
}
1524+
1525+
if boundary {
1526+
stTrie, stEntries = makeBoundaryStorageTrie(slots, db)
1527+
} else if uneven {
1528+
stTrie, stEntries = makeUnevenStorageTrie(slots, db)
1529+
} else {
1530+
stTrie, stEntries = makeStorageTrieWithSeed(uint64(slots), 0, db)
1531+
}
1532+
stRoot := stTrie.Hash()
1533+
14961534
value, _ := rlp.EncodeToBytes(types.StateAccount{
14971535
Nonce: i,
14981536
Balance: big.NewInt(int64(i)),
@@ -1507,8 +1545,8 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie
15071545
storageEntries[common.BytesToHash(key)] = stEntries
15081546
}
15091547
sort.Sort(entries)
1510-
stTrie.Commit(nil)
15111548
accTrie.Commit(nil)
1549+
15121550
return accTrie, entries, storageTries, storageEntries
15131551
}
15141552

@@ -1586,6 +1624,39 @@ func makeBoundaryStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice)
15861624
return trie, entries
15871625
}
15881626

1627+
// makeUnevenStorageTrie constructs a storage tries will states distributed in
1628+
// different range unevenly.
1629+
func makeUnevenStorageTrie(slots int, db *trie.Database) (*trie.Trie, []*kv) {
1630+
var (
1631+
entries entrySlice
1632+
// tr, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db)
1633+
tr, _ = trie.New(common.Hash{}, db)
1634+
chosen = make(map[byte]struct{})
1635+
)
1636+
for i := 0; i < 3; i++ {
1637+
var n int
1638+
for {
1639+
n = mrand.Intn(15) // the last range is set empty deliberately
1640+
if _, ok := chosen[byte(n)]; ok {
1641+
continue
1642+
}
1643+
chosen[byte(n)] = struct{}{}
1644+
break
1645+
}
1646+
for j := 0; j < slots/3; j++ {
1647+
key := append([]byte{byte(n)}, testrand.Bytes(31)...)
1648+
val, _ := rlp.EncodeToBytes(testrand.Bytes(32))
1649+
1650+
elem := &kv{key, val}
1651+
tr.Update(elem.k, elem.v)
1652+
entries = append(entries, elem)
1653+
}
1654+
}
1655+
sort.Sort(entries)
1656+
tr.Commit(nil)
1657+
return tr, entries
1658+
}
1659+
15891660
func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) {
15901661
t.Helper()
15911662
triedb := trie.NewDatabase(db)

internal/testrand/rand.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package testrand
18+
19+
import (
20+
crand "crypto/rand"
21+
"encoding/binary"
22+
mrand "math/rand"
23+
24+
"github.com/ethereum/go-ethereum/common"
25+
)
26+
27+
// prng is a pseudo random number generator seeded by strong randomness.
28+
// The randomness is printed on startup in order to make failures reproducible.
29+
var prng = initRand()
30+
31+
func initRand() *mrand.Rand {
32+
var seed [8]byte
33+
crand.Read(seed[:])
34+
rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:]))))
35+
return rnd
36+
}
37+
38+
// Bytes generates a random byte slice with specified length.
39+
func Bytes(n int) []byte {
40+
r := make([]byte, n)
41+
prng.Read(r)
42+
return r
43+
}
44+
45+
// Hash generates a random hash.
46+
func Hash() common.Hash {
47+
return common.BytesToHash(Bytes(common.HashLength))
48+
}
49+
50+
// Address generates a random address.
51+
func Address() common.Address {
52+
return common.BytesToAddress(Bytes(common.AddressLength))
53+
}

0 commit comments

Comments
 (0)