Skip to content

Commit 1cb3b6a

Browse files
authored
eth/protocols/snap: fix snap sync failure on empty storage range (#28306)
This change addresses an issue in snap sync, specifically when the entire sync process can be halted due to an encountered empty storage range. Currently, on the snap sync client side, the response to an empty (partial) storage range is discarded as a non-delivery. However, this response can be a valid response, when the particular range requested does not contain any slots. For instance, consider a large contract where the entire key space is divided into 16 chunks, and there are no available slots in the last chunk [0xf] -> [end]. When the node receives a request for this particular range, the response includes: The proof with origin [0xf] A nil storage slot set If we simply discard this response, the finalization of the last range will be skipped, halting the entire sync process indefinitely. The test case TestSyncWithUnevenStorage can reproduce the scenario described above. In addition, this change also defines the common variables MaxAddress and MaxHash.
1 parent 2f66d7c commit 1cb3b6a

File tree

10 files changed

+128
-40
lines changed

10 files changed

+128
-40
lines changed

cmd/clef/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,7 @@ func GenDoc(ctx *cli.Context) error {
12061206
URL: accounts.URL{Path: ".. ignored .."},
12071207
},
12081208
{
1209-
Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
1209+
Address: common.MaxAddress,
12101210
},
12111211
}})
12121212
}

cmd/devp2p/internal/ethtest/snap.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type accRangeTest struct {
5858
func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
5959
var (
6060
root = s.chain.RootAt(999)
61-
ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
61+
ffHash = common.MaxHash
6262
zero = common.Hash{}
6363
firstKeyMinus1 = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf29")
6464
firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a")
@@ -125,7 +125,7 @@ type stRangesTest struct {
125125
// TestSnapGetStorageRanges various forms of GetStorageRanges requests.
126126
func (s *Suite) TestSnapGetStorageRanges(t *utesting.T) {
127127
var (
128-
ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
128+
ffHash = common.MaxHash
129129
zero = common.Hash{}
130130
firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a")
131131
secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606")

common/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ const (
4444
var (
4545
hashT = reflect.TypeOf(Hash{})
4646
addressT = reflect.TypeOf(Address{})
47+
48+
// MaxAddress represents the maximum possible address value.
49+
MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
50+
51+
// MaxHash represents the maximum possible hash value.
52+
MaxHash = HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
4753
)
4854

4955
// Hash represents the 32 byte Keccak256 hash of arbitrary data.

core/state_processor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestStateProcessorErrors(t *testing.T) {
132132
)
133133

134134
defer blockchain.Stop()
135-
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
135+
bigNumber := new(big.Int).SetBytes(common.MaxHash.Bytes())
136136
tooBigNumber := new(big.Int).Set(bigNumber)
137137
tooBigNumber.Add(tooBigNumber, common.Big1)
138138
for i, tt := range []struct {

eth/protocols/snap/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP
367367
if len(req.Origin) > 0 {
368368
origin, req.Origin = common.BytesToHash(req.Origin), nil
369369
}
370-
var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
370+
var limit = common.MaxHash
371371
if len(req.Limit) > 0 {
372372
limit, req.Limit = common.BytesToHash(req.Limit), nil
373373
}

eth/protocols/snap/range.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (r *hashRange) End() common.Hash {
6767
// If the end overflows (non divisible range), return a shorter interval
6868
next, overflow := new(uint256.Int).AddOverflow(r.current, r.step)
6969
if overflow {
70-
return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
70+
return common.MaxHash
7171
}
7272
return next.SubUint64(next, 1).Bytes32()
7373
}

eth/protocols/snap/range_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestHashRanges(t *testing.T) {
4545
common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
4646
common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
4747
common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
48-
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
48+
common.MaxHash,
4949
},
5050
},
5151
// Split a divisible part of the hash range up into 2 chunks
@@ -58,7 +58,7 @@ func TestHashRanges(t *testing.T) {
5858
},
5959
ends: []common.Hash{
6060
common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
61-
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
61+
common.MaxHash,
6262
},
6363
},
6464
// Split the entire hash range into a non divisible 3 chunks
@@ -73,7 +73,7 @@ func TestHashRanges(t *testing.T) {
7373
ends: []common.Hash{
7474
common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"),
7575
common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
76-
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
76+
common.MaxHash,
7777
},
7878
},
7979
// Split a part of hash range into a non divisible 3 chunks
@@ -88,7 +88,7 @@ func TestHashRanges(t *testing.T) {
8888
ends: []common.Hash{
8989
common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
9090
common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"),
91-
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
91+
common.MaxHash,
9292
},
9393
},
9494
// Split a part of hash range into a non divisible 3 chunks, but with a
@@ -108,7 +108,7 @@ func TestHashRanges(t *testing.T) {
108108
ends: []common.Hash{
109109
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
110110
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"),
111-
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
111+
common.MaxHash,
112112
},
113113
},
114114
}

eth/protocols/snap/sync.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ func (s *Syncer) loadSyncStatus() {
798798
last := common.BigToHash(new(big.Int).Add(next.Big(), step))
799799
if i == accountConcurrency-1 {
800800
// Make sure we don't overflow if the step is not a proper divisor
801-
last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
801+
last = common.MaxHash
802802
}
803803
batch := ethdb.HookedBatch{
804804
Batch: s.db.NewBatch(),
@@ -1874,7 +1874,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
18741874
return
18751875
}
18761876
// Some accounts are incomplete, leave as is for the storage and contract
1877-
// task assigners to pick up and fill.
1877+
// task assigners to pick up and fill
18781878
}
18791879

18801880
// processBytecodeResponse integrates an already validated bytecode response
@@ -2624,7 +2624,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
26242624
// the requested data. For storage range queries that means the state being
26252625
// retrieved was either already pruned remotely, or the peer is not yet
26262626
// synced to our head.
2627-
if len(hashes) == 0 {
2627+
if len(hashes) == 0 && len(proof) == 0 {
26282628
logger.Debug("Peer rejected storage request")
26292629
s.statelessPeers[peer.ID()] = struct{}{}
26302630
s.lock.Unlock()
@@ -2636,6 +2636,13 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
26362636
// Reconstruct the partial tries from the response and verify them
26372637
var cont bool
26382638

2639+
// If a proof was attached while the response is empty, it indicates that the
2640+
// requested range specified with 'origin' is empty. Construct an empty state
2641+
// response locally to finalize the range.
2642+
if len(hashes) == 0 && len(proof) > 0 {
2643+
hashes = append(hashes, []common.Hash{})
2644+
slots = append(slots, [][]byte{})
2645+
}
26392646
for i := 0; i < len(hashes); i++ {
26402647
// Convert the keys and proofs into an internal format
26412648
keys := make([][]byte, len(hashes[i]))

0 commit comments

Comments
 (0)