Skip to content

Commit 6465032

Browse files
holimanfjl
andauthored
eth/protocols/snap: sort trienode heal requests by path (#24779)
* sort snap trienode heal requests * eth/protocols/snap: remove debug code * eth/protocols/snap: simplify sort, generate pathsets later * eth/protocols/snap: review concern * eth/protocols/snap: renamings * eth/protocols/snap: add comments in Merge * eth/protocols/snap: remove variable 'last' in Merge * eth/protocols/snap: fix lint flaws in test Co-authored-by: Felix Lange <[email protected]>
1 parent 52eb87d commit 6465032

File tree

3 files changed

+187
-4
lines changed

3 files changed

+187
-4
lines changed

eth/protocols/snap/sort_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2022 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 snap
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"testing"
23+
24+
"github.com/ethereum/go-ethereum/common"
25+
"github.com/ethereum/go-ethereum/trie"
26+
)
27+
28+
func hexToNibbles(s string) []byte {
29+
if len(s) >= 2 && s[0] == '0' && s[1] == 'x' {
30+
s = s[2:]
31+
}
32+
var s2 []byte
33+
for _, ch := range []byte(s) {
34+
s2 = append(s2, '0')
35+
s2 = append(s2, ch)
36+
}
37+
return common.Hex2Bytes(string(s2))
38+
}
39+
40+
func TestRequestSorting(t *testing.T) {
41+
42+
// - Path 0x9 -> {0x19}
43+
// - Path 0x99 -> {0x0099}
44+
// - Path 0x01234567890123456789012345678901012345678901234567890123456789019 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19}
45+
// - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099}
46+
var f = func(path string) (trie.SyncPath, TrieNodePathSet, common.Hash) {
47+
data := hexToNibbles(path)
48+
sp := trie.NewSyncPath(data)
49+
tnps := TrieNodePathSet([][]byte(sp))
50+
hash := common.Hash{}
51+
return sp, tnps, hash
52+
}
53+
var (
54+
hashes []common.Hash
55+
paths []trie.SyncPath
56+
pathsets []TrieNodePathSet
57+
)
58+
for _, x := range []string{
59+
"0x9",
60+
"0x012345678901234567890123456789010123456789012345678901234567890195",
61+
"0x012345678901234567890123456789010123456789012345678901234567890197",
62+
"0x012345678901234567890123456789010123456789012345678901234567890196",
63+
"0x99",
64+
"0x012345678901234567890123456789010123456789012345678901234567890199",
65+
"0x01234567890123456789012345678901012345678901234567890123456789019",
66+
"0x0123456789012345678901234567890101234567890123456789012345678901",
67+
"0x01234567890123456789012345678901012345678901234567890123456789010",
68+
"0x01234567890123456789012345678901012345678901234567890123456789011",
69+
} {
70+
sp, tnps, hash := f(x)
71+
hashes = append(hashes, hash)
72+
paths = append(paths, sp)
73+
pathsets = append(pathsets, tnps)
74+
}
75+
_, paths, pathsets = sortByAccountPath(hashes, paths)
76+
{
77+
var b = new(bytes.Buffer)
78+
for i := 0; i < len(paths); i++ {
79+
fmt.Fprintf(b, "\n%d. paths %x", i, paths[i])
80+
}
81+
want := `
82+
0. paths [0099]
83+
1. paths [0123456789012345678901234567890101234567890123456789012345678901 00]
84+
2. paths [0123456789012345678901234567890101234567890123456789012345678901 0095]
85+
3. paths [0123456789012345678901234567890101234567890123456789012345678901 0096]
86+
4. paths [0123456789012345678901234567890101234567890123456789012345678901 0097]
87+
5. paths [0123456789012345678901234567890101234567890123456789012345678901 0099]
88+
6. paths [0123456789012345678901234567890101234567890123456789012345678901 10]
89+
7. paths [0123456789012345678901234567890101234567890123456789012345678901 11]
90+
8. paths [0123456789012345678901234567890101234567890123456789012345678901 19]
91+
9. paths [19]`
92+
if have := b.String(); have != want {
93+
t.Errorf("have:%v\nwant:%v\n", have, want)
94+
}
95+
}
96+
{
97+
var b = new(bytes.Buffer)
98+
for i := 0; i < len(pathsets); i++ {
99+
fmt.Fprintf(b, "\n%d. pathset %x", i, pathsets[i])
100+
}
101+
want := `
102+
0. pathset [0099]
103+
1. pathset [0123456789012345678901234567890101234567890123456789012345678901 00 0095 0096 0097 0099 10 11 19]
104+
2. pathset [19]`
105+
if have := b.String(); have != want {
106+
t.Errorf("have:%v\nwant:%v\n", have, want)
107+
}
108+
}
109+
}

eth/protocols/snap/sync.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1331,12 +1331,13 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai
13311331

13321332
hashes = append(hashes, hash)
13331333
paths = append(paths, pathset)
1334-
pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash
13351334

13361335
if len(hashes) >= cap {
13371336
break
13381337
}
13391338
}
1339+
// Group requests by account hash
1340+
hashes, paths, pathsets = sortByAccountPath(hashes, paths)
13401341
req := &trienodeHealRequest{
13411342
peer: idle,
13421343
id: reqid,
@@ -2908,3 +2909,76 @@ func (s *capacitySort) Swap(i, j int) {
29082909
s.ids[i], s.ids[j] = s.ids[j], s.ids[i]
29092910
s.caps[i], s.caps[j] = s.caps[j], s.caps[i]
29102911
}
2912+
2913+
// healRequestSort implements the Sort interface, allowing sorting trienode
2914+
// heal requests, which is a prerequisite for merging storage-requests.
2915+
type healRequestSort struct {
2916+
hashes []common.Hash
2917+
paths []trie.SyncPath
2918+
}
2919+
2920+
func (t *healRequestSort) Len() int {
2921+
return len(t.hashes)
2922+
}
2923+
2924+
func (t *healRequestSort) Less(i, j int) bool {
2925+
a := t.paths[i]
2926+
b := t.paths[j]
2927+
switch bytes.Compare(a[0], b[0]) {
2928+
case -1:
2929+
return true
2930+
case 1:
2931+
return false
2932+
}
2933+
// identical first part
2934+
if len(a) < len(b) {
2935+
return true
2936+
}
2937+
if len(b) < len(a) {
2938+
return false
2939+
}
2940+
if len(a) == 2 {
2941+
return bytes.Compare(a[1], b[1]) < 0
2942+
}
2943+
return false
2944+
}
2945+
2946+
func (t *healRequestSort) Swap(i, j int) {
2947+
t.hashes[i], t.hashes[j] = t.hashes[j], t.hashes[i]
2948+
t.paths[i], t.paths[j] = t.paths[j], t.paths[i]
2949+
}
2950+
2951+
// Merge merges the pathsets, so that several storage requests concerning the
2952+
// same account are merged into one, to reduce bandwidth.
2953+
// OBS: This operation is moot if t has not first been sorted.
2954+
func (t *healRequestSort) Merge() []TrieNodePathSet {
2955+
var result []TrieNodePathSet
2956+
for _, path := range t.paths {
2957+
pathset := TrieNodePathSet([][]byte(path))
2958+
if len(path) == 1 {
2959+
// It's an account reference.
2960+
result = append(result, pathset)
2961+
} else {
2962+
// It's a storage reference.
2963+
end := len(result) - 1
2964+
if len(result) == 0 || !bytes.Equal(pathset[0], result[end][0]) {
2965+
// The account doesn't doesn't match last, create a new entry.
2966+
result = append(result, pathset)
2967+
} else {
2968+
// It's the same account as the previous one, add to the storage
2969+
// paths of that request.
2970+
result[end] = append(result[end], pathset[1])
2971+
}
2972+
}
2973+
}
2974+
return result
2975+
}
2976+
2977+
// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates
2978+
// the TrieNodePaths and merges paths which belongs to the same account path.
2979+
func sortByAccountPath(hashes []common.Hash, paths []trie.SyncPath) ([]common.Hash, []trie.SyncPath, []TrieNodePathSet) {
2980+
n := &healRequestSort{hashes, paths}
2981+
sort.Sort(n)
2982+
pathsets := n.Merge()
2983+
return n.hashes, n.paths, pathsets
2984+
}

trie/sync.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ type request struct {
7171
// - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099}
7272
type SyncPath [][]byte
7373

74-
// newSyncPath converts an expanded trie path from nibble form into a compact
74+
// NewSyncPath converts an expanded trie path from nibble form into a compact
7575
// version that can be sent over the network.
76-
func newSyncPath(path []byte) SyncPath {
76+
func NewSyncPath(path []byte) SyncPath {
7777
// If the hash is from the account trie, append a single item, if it
7878
// is from the a storage trie, append a tuple. Note, the length 64 is
7979
// clashing between account leaf and storage root. It's fine though
@@ -238,7 +238,7 @@ func (s *Sync) Missing(max int) (nodes []common.Hash, paths []SyncPath, codes []
238238
hash := item.(common.Hash)
239239
if req, ok := s.nodeReqs[hash]; ok {
240240
nodeHashes = append(nodeHashes, hash)
241-
nodePaths = append(nodePaths, newSyncPath(req.path))
241+
nodePaths = append(nodePaths, NewSyncPath(req.path))
242242
} else {
243243
codeHashes = append(codeHashes, hash)
244244
}

0 commit comments

Comments
 (0)