Skip to content

Commit

Permalink
eth/protocols/snap: generate storage trie from full dirty snap data (e…
Browse files Browse the repository at this point in the history
…thereum#22668)

* eth/protocols/snap: generate storage trie from full dirty snap data

* eth/protocols/snap: get rid of some more dead code

* eth/protocols/snap: less frequent logs, also log during trie generation

* eth/protocols/snap: implement dirty account range stack-hashing

* eth/protocols/snap: don't loop on account trie generation

* eth/protocols/snap: fix account format in trie

* core, eth, ethdb: glue snap packets together, but not chunks

* eth/protocols/snap: print completion log for snap phase

* eth/protocols/snap: extended tests

* eth/protocols/snap: make testcase pass

* eth/protocols/snap: fix account stacktrie commit without defer

* ethdb: fix key counts on reset

* eth/protocols: fix typos

* eth/protocols/snap: make better use of delivered data (#44)

* eth/protocols/snap: make better use of delivered data

* squashme

* eth/protocols/snap: reduce chunking

* squashme

* eth/protocols/snap: reduce chunking further

* eth/protocols/snap: break out hash range calculations

* eth/protocols/snap: use sort.Search instead of looping

* eth/protocols/snap: prevent crash on storage response with no keys

* eth/protocols/snap: nitpicks all around

* eth/protocols/snap: clear heal need on 1-chunk storage completion

* eth/protocols/snap: fix range chunker, add tests

Co-authored-by: Péter Szilágyi <peterke@gmail.com>

* trie: fix test API error

* eth/protocols/snap: fix some further liter issues

* eth/protocols/snap: fix accidental batch reuse

Co-authored-by: Martin Holst Swende <martin@swende.se>
  • Loading branch information
karalabe and holiman committed Apr 27, 2021
1 parent 65a1c2d commit caea6c4
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 188 deletions.
17 changes: 17 additions & 0 deletions core/rawdb/database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package rawdb
5 changes: 5 additions & 0 deletions core/rawdb/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ func (b *tableBatch) Delete(key []byte) error {
return b.batch.Delete(append([]byte(b.prefix), key...))
}

// KeyCount retrieves the number of keys queued up for writing.
func (b *tableBatch) KeyCount() int {
return b.batch.KeyCount()
}

// ValueSize retrieves the amount of data queued up for writing.
func (b *tableBatch) ValueSize() int {
return b.batch.ValueSize()
Expand Down
2 changes: 1 addition & 1 deletion eth/protocols/snap/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ func handleMessage(backend Backend, peer *Peer) error {
if err := msg.Decode(res); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
}
// Ensure the ranges ae monotonically increasing
// Ensure the ranges are monotonically increasing
for i, slots := range res.Slots {
for j := 1; j < len(slots); j++ {
if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 {
Expand Down
80 changes: 80 additions & 0 deletions eth/protocols/snap/range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package snap

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)

// hashRange is a utility to handle ranges of hashes, Split up the
// hash-space into sections, and 'walk' over the sections
type hashRange struct {
current *uint256.Int
step *uint256.Int
}

// newHashRange creates a new hashRange, initiated at the start position,
// and with the step set to fill the desired 'num' chunks
func newHashRange(start common.Hash, num uint64) *hashRange {
left := new(big.Int).Sub(hashSpace, start.Big())
step := new(big.Int).Div(
new(big.Int).Add(left, new(big.Int).SetUint64(num-1)),
new(big.Int).SetUint64(num),
)
step256 := new(uint256.Int)
step256.SetFromBig(step)

return &hashRange{
current: uint256.NewInt().SetBytes32(start[:]),
step: step256,
}
}

// Next pushes the hash range to the next interval.
func (r *hashRange) Next() bool {
next := new(uint256.Int)
if overflow := next.AddOverflow(r.current, r.step); overflow {
return false
}
r.current = next
return true
}

// Start returns the first hash in the current interval.
func (r *hashRange) Start() common.Hash {
return r.current.Bytes32()
}

// End returns the last hash in the current interval.
func (r *hashRange) End() common.Hash {
// If the end overflows (non divisible range), return a shorter interval
next := new(uint256.Int)
if overflow := next.AddOverflow(r.current, r.step); overflow {
return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
}
return new(uint256.Int).Sub(next, uint256.NewInt().SetOne()).Bytes32()
}

// incHash returns the next hash, in lexicographical order (a.k.a plus one)
func incHash(h common.Hash) common.Hash {
a := uint256.NewInt().SetBytes32(h[:])
a.Add(a, uint256.NewInt().SetOne())
return common.Hash(a.Bytes32())
}
143 changes: 143 additions & 0 deletions eth/protocols/snap/range_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package snap

import (
"testing"

"github.com/ethereum/go-ethereum/common"
)

// Tests that given a starting hash and a density, the hash ranger can correctly
// split up the remaining hash space into a fixed number of chunks.
func TestHashRanges(t *testing.T) {
tests := []struct {
head common.Hash
chunks uint64
starts []common.Hash
ends []common.Hash
}{
// Simple test case to split the entire hash range into 4 chunks
{
head: common.Hash{},
chunks: 4,
starts: []common.Hash{
{},
common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000"),
common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000"),
common.HexToHash("0xc000000000000000000000000000000000000000000000000000000000000000"),
},
ends: []common.Hash{
common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
},
// Split a divisible part of the hash range up into 2 chunks
{
head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
chunks: 2,
starts: []common.Hash{
common.Hash{},
common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"),
},
ends: []common.Hash{
common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
},
// Split the entire hash range into a non divisible 3 chunks
{
head: common.Hash{},
chunks: 3,
starts: []common.Hash{
{},
common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555556"),
common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
},
ends: []common.Hash{
common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"),
common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
},
// Split a part of hash range into a non divisible 3 chunks
{
head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
chunks: 3,
starts: []common.Hash{
{},
common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555556"),
},
ends: []common.Hash{
common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"),
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
},
// Split a part of hash range into a non divisible 3 chunks, but with a
// meaningful space size for manual verification.
// - The head being 0xff...f0, we have 14 hashes left in the space
// - Chunking up 14 into 3 pieces is 4.(6), but we need the ceil of 5 to avoid a micro-last-chunk
// - Since the range is not divisible, the last interval will be shrter, capped at 0xff...f
// - The chunk ranges thus needs to be [..0, ..5], [..6, ..b], [..c, ..f]
{
head: common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
chunks: 3,
starts: []common.Hash{
{},
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
},
ends: []common.Hash{
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"),
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
},
},
}
for i, tt := range tests {
r := newHashRange(tt.head, tt.chunks)

var (
starts = []common.Hash{{}}
ends = []common.Hash{r.End()}
)
for r.Next() {
starts = append(starts, r.Start())
ends = append(ends, r.End())
}
if len(starts) != len(tt.starts) {
t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts))
}
for j := 0; j < len(starts) && j < len(tt.starts); j++ {
if starts[j] != tt.starts[j] {
t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j])
}
}
if len(ends) != len(tt.ends) {
t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends))
}
for j := 0; j < len(ends) && j < len(tt.ends); j++ {
if ends[j] != tt.ends[j] {
t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j])
}
}
}
}

0 comments on commit caea6c4

Please sign in to comment.