mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 04:36:42 +00:00
rlp, trie, triedb/pathdb: compress trienode hsitory
This commit is contained in:
parent
e20b05ec7f
commit
0d0c8778a1
5 changed files with 669 additions and 0 deletions
30
rlp/raw.go
30
rlp/raw.go
|
|
@ -17,6 +17,7 @@
|
||||||
package rlp
|
package rlp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
@ -152,6 +153,35 @@ func CountValues(b []byte) (int, error) {
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitListValues extracts the raw elements from the list RLP-encoding blob.
|
||||||
|
func SplitListValues(b []byte) ([][]byte, error) {
|
||||||
|
b, _, err := SplitList(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode error: %v", err)
|
||||||
|
}
|
||||||
|
var elements [][]byte
|
||||||
|
for len(b) > 0 {
|
||||||
|
_, tagsize, size, err := readKind(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
elements = append(elements, b[:tagsize+size])
|
||||||
|
b = b[tagsize+size:]
|
||||||
|
}
|
||||||
|
return elements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeListValues takes a list of raw elements and rlp-encodes them as list.
|
||||||
|
func MergeListValues(elems [][]byte) ([]byte, error) {
|
||||||
|
w := NewEncoderBuffer(nil)
|
||||||
|
offset := w.List()
|
||||||
|
for _, elem := range elems {
|
||||||
|
w.Write(elem)
|
||||||
|
}
|
||||||
|
w.ListEnd(offset)
|
||||||
|
return w.ToBytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) {
|
func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) {
|
||||||
if len(buf) == 0 {
|
if len(buf) == 0 {
|
||||||
return 0, 0, 0, io.ErrUnexpectedEOF
|
return 0, 0, 0, io.ErrUnexpectedEOF
|
||||||
|
|
|
||||||
69
trie/node.go
69
trie/node.go
|
|
@ -17,6 +17,7 @@
|
||||||
package trie
|
package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -242,6 +243,74 @@ func decodeRef(buf []byte) (node, []byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeNodeElements parses the RLP encoding of a trie node and returns all the
|
||||||
|
// elements in raw byte format.
|
||||||
|
//
|
||||||
|
// For full node, it returns a slice of 17 elements;
|
||||||
|
// For short node, it returns a slice of 2 elements;
|
||||||
|
func decodeNodeElements(buf []byte) ([][]byte, error) {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return rlp.SplitListValues(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeNodeElements encodes the provided node elements into a rlp list.
|
||||||
|
func encodeNodeElements(elements [][]byte) ([]byte, error) {
|
||||||
|
if len(elements) != 2 && len(elements) != 17 {
|
||||||
|
return nil, fmt.Errorf("invalid number of elements: %d", len(elements))
|
||||||
|
}
|
||||||
|
return rlp.MergeListValues(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeDifference accepts two RLP-encoding nodes and figures out the difference
|
||||||
|
// between them.
|
||||||
|
//
|
||||||
|
// An error is returned if any of the provided blob is nil, or the type of nodes
|
||||||
|
// are different.
|
||||||
|
func NodeDifference(oldvalue []byte, newvalue []byte) (int, []int, [][]byte, error) {
|
||||||
|
oldElems, err := decodeNodeElements(oldvalue)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, nil, err
|
||||||
|
}
|
||||||
|
newElems, err := decodeNodeElements(newvalue)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, nil, err
|
||||||
|
}
|
||||||
|
if len(oldElems) != len(newElems) {
|
||||||
|
return 0, nil, nil, fmt.Errorf("different node type, old elements: %d, new elements: %d", len(oldElems), len(newElems))
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
indices = make([]int, 0, len(oldElems))
|
||||||
|
diff = make([][]byte, 0, len(oldElems))
|
||||||
|
)
|
||||||
|
for i := 0; i < len(oldElems); i++ {
|
||||||
|
if !bytes.Equal(oldElems[i], newElems[i]) {
|
||||||
|
indices = append(indices, i)
|
||||||
|
diff = append(diff, oldElems[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(oldElems), indices, diff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReassembleNode accepts a RLP-encoding node along with a set of mutations,
|
||||||
|
// applying the modification diffs according to the indices and re-assemble.
|
||||||
|
func ReassembleNode(blob []byte, mutations [][][]byte, indices [][]int) ([]byte, error) {
|
||||||
|
if len(mutations) == 0 && len(indices) == 0 {
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
|
elements, err := decodeNodeElements(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(mutations); i++ {
|
||||||
|
for j, pos := range indices[i] {
|
||||||
|
elements[pos] = mutations[i][j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encodeNodeElements(elements)
|
||||||
|
}
|
||||||
|
|
||||||
// wraps a decoding error with information about the path to the
|
// wraps a decoding error with information about the path to the
|
||||||
// invalid child node (for debugging encoding issues).
|
// invalid child node (for debugging encoding issues).
|
||||||
type decodeError struct {
|
type decodeError struct {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,12 @@ package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -94,6 +97,286 @@ func TestDecodeFullNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestLeafNode(small bool) []byte {
|
||||||
|
l := leafNodeEncoder{}
|
||||||
|
l.Key = hexToCompact(keybytesToHex(testrand.Bytes(10)))
|
||||||
|
if small {
|
||||||
|
l.Val = testrand.Bytes(10)
|
||||||
|
} else {
|
||||||
|
l.Val = testrand.Bytes(32)
|
||||||
|
}
|
||||||
|
buf := rlp.NewEncoderBuffer(nil)
|
||||||
|
l.encode(buf)
|
||||||
|
return buf.ToBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestFullNode(small bool) []byte {
|
||||||
|
n := fullnodeEncoder{}
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
switch rand.Intn(3) {
|
||||||
|
case 0:
|
||||||
|
// write nil
|
||||||
|
case 1:
|
||||||
|
// write hash
|
||||||
|
n.Children[i] = testrand.Bytes(32)
|
||||||
|
case 2:
|
||||||
|
// write embedded node
|
||||||
|
n.Children[i] = makeTestLeafNode(small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.Children[16] = testrand.Bytes(32) // value
|
||||||
|
buf := rlp.NewEncoderBuffer(nil)
|
||||||
|
n.encode(buf)
|
||||||
|
return buf.ToBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeNodeElements(t *testing.T) {
|
||||||
|
var nodes [][]byte
|
||||||
|
nodes = append(nodes, makeTestFullNode(true))
|
||||||
|
nodes = append(nodes, makeTestFullNode(false))
|
||||||
|
nodes = append(nodes, makeTestLeafNode(true))
|
||||||
|
nodes = append(nodes, makeTestLeafNode(false))
|
||||||
|
|
||||||
|
for _, blob := range nodes {
|
||||||
|
elements, err := decodeNodeElements(blob)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode node elements: %v", err)
|
||||||
|
}
|
||||||
|
enc, err := encodeNodeElements(elements)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to encode node elements: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(enc, blob) {
|
||||||
|
t.Fatalf("Unexpected encoded node element, want: %v, got: %v", blob, enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestLeafNodePair() ([]byte, []byte, [][]byte, []int) {
|
||||||
|
var (
|
||||||
|
na = leafNodeEncoder{}
|
||||||
|
nb = leafNodeEncoder{}
|
||||||
|
)
|
||||||
|
key := keybytesToHex(testrand.Bytes(10))
|
||||||
|
na.Key = hexToCompact(key)
|
||||||
|
nb.Key = hexToCompact(key)
|
||||||
|
|
||||||
|
valA := testrand.Bytes(32)
|
||||||
|
valB := testrand.Bytes(32)
|
||||||
|
na.Val = valA
|
||||||
|
nb.Val = valB
|
||||||
|
|
||||||
|
bufa, bufb := rlp.NewEncoderBuffer(nil), rlp.NewEncoderBuffer(nil)
|
||||||
|
na.encode(bufa)
|
||||||
|
nb.encode(bufb)
|
||||||
|
diff, _ := rlp.EncodeToBytes(valA)
|
||||||
|
return bufa.ToBytes(), bufb.ToBytes(), [][]byte{diff}, []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestFullNodePair() ([]byte, []byte, [][]byte, []int) {
|
||||||
|
var (
|
||||||
|
na = fullnodeEncoder{}
|
||||||
|
nb = fullnodeEncoder{}
|
||||||
|
indices []int
|
||||||
|
values [][]byte
|
||||||
|
)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
switch rand.Intn(3) {
|
||||||
|
case 0:
|
||||||
|
// write nil
|
||||||
|
case 1:
|
||||||
|
// write same
|
||||||
|
var child []byte
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
child = testrand.Bytes(32) // hashnode
|
||||||
|
} else {
|
||||||
|
child = makeTestLeafNode(true) // embedded node
|
||||||
|
}
|
||||||
|
na.Children[i] = child
|
||||||
|
nb.Children[i] = child
|
||||||
|
case 2:
|
||||||
|
// write different
|
||||||
|
var (
|
||||||
|
va []byte
|
||||||
|
diff []byte
|
||||||
|
)
|
||||||
|
rnd := rand.Intn(3)
|
||||||
|
if rnd == 0 {
|
||||||
|
va = testrand.Bytes(32) // hashnode
|
||||||
|
diff, _ = rlp.EncodeToBytes(va)
|
||||||
|
} else if rnd == 1 {
|
||||||
|
va = makeTestLeafNode(true) // embedded node
|
||||||
|
diff = va
|
||||||
|
} else {
|
||||||
|
va = nil
|
||||||
|
diff = rlp.EmptyString
|
||||||
|
}
|
||||||
|
vb := testrand.Bytes(32) // hashnode
|
||||||
|
na.Children[i] = va
|
||||||
|
nb.Children[i] = vb
|
||||||
|
|
||||||
|
indices = append(indices, i)
|
||||||
|
values = append(values, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
na.Children[16] = nil
|
||||||
|
nb.Children[16] = nil
|
||||||
|
|
||||||
|
bufa, bufb := rlp.NewEncoderBuffer(nil), rlp.NewEncoderBuffer(nil)
|
||||||
|
na.encode(bufa)
|
||||||
|
nb.encode(bufb)
|
||||||
|
return bufa.ToBytes(), bufb.ToBytes(), values, indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeDifference(t *testing.T) {
|
||||||
|
type testsuite struct {
|
||||||
|
old []byte
|
||||||
|
new []byte
|
||||||
|
expErr bool
|
||||||
|
expIndices []int
|
||||||
|
expValues [][]byte
|
||||||
|
}
|
||||||
|
var tests = []testsuite{
|
||||||
|
// Invalid node data
|
||||||
|
{
|
||||||
|
old: nil, new: nil, expErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
old: testrand.Bytes(32), new: nil, expErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
old: nil, new: testrand.Bytes(32), expErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
old: testrand.Bytes(32), new: testrand.Bytes(32), expErr: true,
|
||||||
|
},
|
||||||
|
// Different node type
|
||||||
|
{
|
||||||
|
old: makeTestLeafNode(true), new: makeTestFullNode(true), expErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for range 10 {
|
||||||
|
va, vb, elements, indices := makeTestLeafNodePair()
|
||||||
|
tests = append(tests, testsuite{
|
||||||
|
old: va,
|
||||||
|
new: vb,
|
||||||
|
expErr: false,
|
||||||
|
expIndices: indices,
|
||||||
|
expValues: elements,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for range 10 {
|
||||||
|
va, vb, elements, indices := makeTestFullNodePair()
|
||||||
|
tests = append(tests, testsuite{
|
||||||
|
old: va,
|
||||||
|
new: vb,
|
||||||
|
expErr: false,
|
||||||
|
expIndices: indices,
|
||||||
|
expValues: elements,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
_, indices, values, err := NodeDifference(test.old, test.new)
|
||||||
|
if test.expErr && err == nil {
|
||||||
|
t.Fatal("Expect error, got nil")
|
||||||
|
}
|
||||||
|
if !test.expErr && err != nil {
|
||||||
|
t.Fatalf("Unexpect error, %v", err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if !reflect.DeepEqual(indices, test.expIndices) {
|
||||||
|
t.Fatalf("Unexpected indices, want: %v, got: %v", test.expIndices, indices)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(values, test.expValues) {
|
||||||
|
t.Fatalf("Unexpected values, want: %v, got: %v", test.expValues, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReassembleFullNode(t *testing.T) {
|
||||||
|
var fn fullnodeEncoder
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
fn.Children[i] = testrand.Bytes(32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf := rlp.NewEncoderBuffer(nil)
|
||||||
|
fn.encode(buf)
|
||||||
|
enc := buf.ToBytes()
|
||||||
|
|
||||||
|
// Generate a list of diffs
|
||||||
|
var (
|
||||||
|
values [][][]byte
|
||||||
|
indices [][]int
|
||||||
|
)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
var (
|
||||||
|
pos = make(map[int]struct{})
|
||||||
|
poslist []int
|
||||||
|
valuelist [][]byte
|
||||||
|
)
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
p := rand.Intn(16)
|
||||||
|
if _, ok := pos[p]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pos[p] = struct{}{}
|
||||||
|
|
||||||
|
nh := testrand.Bytes(32)
|
||||||
|
diff, _ := rlp.EncodeToBytes(nh)
|
||||||
|
poslist = append(poslist, p)
|
||||||
|
valuelist = append(valuelist, diff)
|
||||||
|
fn.Children[p] = nh
|
||||||
|
}
|
||||||
|
values = append(values, valuelist)
|
||||||
|
indices = append(indices, poslist)
|
||||||
|
}
|
||||||
|
reassembled, err := ReassembleNode(enc, values, indices)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to re-assemble full node %v", err)
|
||||||
|
}
|
||||||
|
buf2 := rlp.NewEncoderBuffer(nil)
|
||||||
|
fn.encode(buf2)
|
||||||
|
enc2 := buf2.ToBytes()
|
||||||
|
if !reflect.DeepEqual(enc2, reassembled) {
|
||||||
|
t.Fatalf("Unexpeted reassembled node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReassembleShortNode(t *testing.T) {
|
||||||
|
var ln leafNodeEncoder
|
||||||
|
ln.Key = hexToCompact(keybytesToHex(testrand.Bytes(10)))
|
||||||
|
ln.Val = testrand.Bytes(10)
|
||||||
|
buf := rlp.NewEncoderBuffer(nil)
|
||||||
|
ln.encode(buf)
|
||||||
|
enc := buf.ToBytes()
|
||||||
|
|
||||||
|
// Generate a list of diffs
|
||||||
|
var (
|
||||||
|
values [][][]byte
|
||||||
|
indices [][]int
|
||||||
|
)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
val := testrand.Bytes(10)
|
||||||
|
ln.Val = val
|
||||||
|
diff, _ := rlp.EncodeToBytes(val)
|
||||||
|
values = append(values, [][]byte{diff})
|
||||||
|
indices = append(indices, []int{1})
|
||||||
|
}
|
||||||
|
reassembled, err := ReassembleNode(enc, values, indices)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to re-assemble full node %v", err)
|
||||||
|
}
|
||||||
|
buf2 := rlp.NewEncoderBuffer(nil)
|
||||||
|
ln.encode(buf2)
|
||||||
|
enc2 := buf2.ToBytes()
|
||||||
|
if !reflect.DeepEqual(enc2, reassembled) {
|
||||||
|
t.Fatalf("Unexpeted reassembled node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// goos: darwin
|
// goos: darwin
|
||||||
// goarch: arm64
|
// goarch: arm64
|
||||||
// pkg: github.com/ethereum/go-ethereum/trie
|
// pkg: github.com/ethereum/go-ethereum/trie
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// 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/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
package pathdb
|
package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
|
|
||||||
|
|
@ -30,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -424,3 +427,239 @@ func (s *nodeSetWithOrigin) decode(r *rlp.Stream) error {
|
||||||
s.computeSize()
|
s.computeSize()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeNodeCompressed encodes the trie node differences between two consecutive
|
||||||
|
// versions into byte stream. The format is as below:
|
||||||
|
//
|
||||||
|
// - metadata byte layout (1 byte):
|
||||||
|
//
|
||||||
|
// ┌──── Bits (from MSB to LSB) ───┐
|
||||||
|
// │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
|
||||||
|
// └───────────────────────────────┘
|
||||||
|
// │ │ │ │ │ │ │ └─ FlagA: marks if value is encoded in compressed format (1 always)
|
||||||
|
// │ │ │ │ │ │ └───── FlagB: marks if no extended bitmap used after the metadata byte
|
||||||
|
// │ │ │ │ │ └───────── FlagC: bitmap for node (only used when flagB == 1)
|
||||||
|
// │ │ │ │ └───────────── FlagD: bitmap for node (only used when flagB == 1)
|
||||||
|
// │ │ │ └───────────────── FlagE: reserved
|
||||||
|
// │ │ └───────────────────── FlagF: reserved
|
||||||
|
// │ └───────────────────────── FlagG: reserved
|
||||||
|
// └───────────────────────────── FlagH: reserved
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// 0b_0000_1011
|
||||||
|
//
|
||||||
|
// Bit0=1, Bit1=1 -> node in compressed format, no extended bitmap
|
||||||
|
// Bit2=0, Bit3=1 -> the key of a short node is not stored; its value is stored.
|
||||||
|
//
|
||||||
|
// - 2 bytes extended bitmap (only if the flagB in metadata is 0), each bit
|
||||||
|
// represents a corresponding child;
|
||||||
|
//
|
||||||
|
// - concatenation of original value of modified children along with its size;
|
||||||
|
func encodeNodeCompressed(addExtension bool, elements [][]byte, indices []int) []byte {
|
||||||
|
var (
|
||||||
|
enc []byte
|
||||||
|
flag = byte(1) // flagA
|
||||||
|
)
|
||||||
|
if !addExtension {
|
||||||
|
flag |= 2 // flagB
|
||||||
|
|
||||||
|
// Embedded bitmap
|
||||||
|
for _, pos := range indices {
|
||||||
|
flag |= 1 << (pos + 2) // flagC and flagD
|
||||||
|
}
|
||||||
|
enc = append(enc, flag)
|
||||||
|
} else {
|
||||||
|
// Extended bitmap
|
||||||
|
bitmap := make([]byte, 2) // bitmaps for at most 16 children
|
||||||
|
for _, pos := range indices {
|
||||||
|
// Children[16] is only theoretically possible in the Merkle-Patricia-trie,
|
||||||
|
// in practice this field is never used in the Ethereum case.
|
||||||
|
if pos == 16 {
|
||||||
|
panic(fmt.Sprintf("Unexpected node children index %d", pos))
|
||||||
|
}
|
||||||
|
bitIndex := uint(pos % 8)
|
||||||
|
bitmap[pos/8] |= 1 << bitIndex
|
||||||
|
}
|
||||||
|
enc = append(enc, flag)
|
||||||
|
enc = append(enc, bitmap...)
|
||||||
|
}
|
||||||
|
for _, element := range elements {
|
||||||
|
enc = append(enc, byte(len(element))) // 1 byte is sufficient for element size
|
||||||
|
enc = append(enc, element...)
|
||||||
|
}
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeNodeFull encodes the full trie node value into byte stream. The format is
|
||||||
|
// as below:
|
||||||
|
//
|
||||||
|
// - metadata byte layout (1 byte): 0b0
|
||||||
|
// - node value
|
||||||
|
func encodeNodeFull(value []byte) []byte {
|
||||||
|
enc := make([]byte, len(value)+1)
|
||||||
|
copy(enc[1:], value)
|
||||||
|
return enc
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeNodeCompressed decodes the byte stream of compressed trie node
|
||||||
|
// back to the original elements and their indices.
|
||||||
|
//
|
||||||
|
// It assumes the byte stream contains a compressed format node.
|
||||||
|
func decodeNodeCompressed(data []byte) ([][]byte, []int, error) {
|
||||||
|
if len(data) < 1 {
|
||||||
|
return nil, nil, errors.New("invalid data: too short")
|
||||||
|
}
|
||||||
|
flag := data[0]
|
||||||
|
if flag&byte(1) == 0 {
|
||||||
|
return nil, nil, errors.New("invalid data: full node value")
|
||||||
|
}
|
||||||
|
noExtend := flag&byte(2) != 0 // flagB
|
||||||
|
|
||||||
|
// Reconstruct indices from bitmap
|
||||||
|
var indices []int
|
||||||
|
if noExtend {
|
||||||
|
if flag&byte(4) != 0 { // flagC
|
||||||
|
indices = append(indices, 0)
|
||||||
|
}
|
||||||
|
if flag&byte(8) != 0 { // flagD
|
||||||
|
indices = append(indices, 1)
|
||||||
|
}
|
||||||
|
data = data[1:]
|
||||||
|
} else {
|
||||||
|
if len(data) < 3 {
|
||||||
|
return nil, nil, errors.New("invalid data: too short")
|
||||||
|
}
|
||||||
|
bitmap := data[1:3]
|
||||||
|
for index, b := range bitmap {
|
||||||
|
for bitIdx := 0; bitIdx < 8; bitIdx++ {
|
||||||
|
if b&(1<<uint(bitIdx)) != 0 {
|
||||||
|
pos := index*8 + bitIdx
|
||||||
|
indices = append(indices, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = data[3:]
|
||||||
|
}
|
||||||
|
// Reconstruct elements
|
||||||
|
elements := make([][]byte, 0, len(indices))
|
||||||
|
for i := 0; i < len(indices); i++ {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, nil, errors.New("invalid data: missing size byte")
|
||||||
|
}
|
||||||
|
// Read element size
|
||||||
|
size := int(data[0])
|
||||||
|
data = data[1:]
|
||||||
|
|
||||||
|
// Check if we have enough data for the element
|
||||||
|
if len(data) < size {
|
||||||
|
return nil, nil, fmt.Errorf("invalid data: expected %d bytes, got %d", size, len(data))
|
||||||
|
}
|
||||||
|
// Extract element
|
||||||
|
if size == 0 {
|
||||||
|
elements = append(elements, nil)
|
||||||
|
|
||||||
|
// The zero-size element is practically unexpected, for node deletion
|
||||||
|
// the rlp.EmptyString is still expected. Log loudly for the potential
|
||||||
|
// programming error.
|
||||||
|
log.Warn("Empty element from compressed node", "raw", data)
|
||||||
|
} else {
|
||||||
|
element := make([]byte, size)
|
||||||
|
copy(element, data[:size])
|
||||||
|
data = data[size:]
|
||||||
|
elements = append(elements, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if all data is consumed
|
||||||
|
if len(data) != 0 {
|
||||||
|
return nil, nil, errors.New("invalid data: trailing bytes")
|
||||||
|
}
|
||||||
|
return elements, indices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeNodeFull decodes the byte stream of full value trie node.
|
||||||
|
func decodeNodeFull(data []byte) ([]byte, error) {
|
||||||
|
if len(data) < 1 {
|
||||||
|
return nil, errors.New("invalid data: too short")
|
||||||
|
}
|
||||||
|
flag := data[0]
|
||||||
|
if flag != byte(0) {
|
||||||
|
return nil, errors.New("invalid data: compressed node value")
|
||||||
|
}
|
||||||
|
return data[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeFullFrequency specifies the frequency (1/16) for encoding node in
|
||||||
|
// full format. TODO(rjl493456442) making it configurable.
|
||||||
|
const encodeFullFrequency = 16
|
||||||
|
|
||||||
|
// encodeNodeHistory encodes the history of a node. Typically, the original values
|
||||||
|
// of dirty nodes serve as the history, but this can lead to significant storage
|
||||||
|
// overhead.
|
||||||
|
//
|
||||||
|
// For full nodes, which often see only a few modified children during state
|
||||||
|
// transitions, recording the entire child set (up to 16 children at 32 bytes
|
||||||
|
// each) is inefficient. For short nodes, which often see only the value is
|
||||||
|
// modified during the state transition, recording the key part is also unnecessary.
|
||||||
|
// To compress size, we instead record the diff of the node, rather than the
|
||||||
|
// full value. It's vital to compress the overall trienode history.
|
||||||
|
//
|
||||||
|
// However, recovering a node from a series of diffs requires applying multiple
|
||||||
|
// history records, which is computationally and IO intensive. To mitigate this, we
|
||||||
|
// periodically record the full value of a node as a checkpoint. The frequency of
|
||||||
|
// these checkpoints is a tradeoff between the compression rate and read overhead.
|
||||||
|
func (s *nodeSetWithOrigin) encodeNodeHistory(root common.Hash) (map[common.Hash]map[string][]byte, error) {
|
||||||
|
var (
|
||||||
|
// the set of all encoded node history elements
|
||||||
|
nodes = make(map[common.Hash]map[string][]byte)
|
||||||
|
|
||||||
|
// encodeFullValue determines whether a node should be encoded
|
||||||
|
// in full format with a pseudo-random probabilistic algorithm.
|
||||||
|
encodeFullValue = func(owner common.Hash, path string) bool {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write(root.Bytes())
|
||||||
|
h.Write(owner.Bytes())
|
||||||
|
h.Write([]byte(path))
|
||||||
|
return h.Sum32()%uint32(encodeFullFrequency) == 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for owner, origins := range s.nodeOrigin {
|
||||||
|
var posts map[string]*trienode.Node
|
||||||
|
if owner == (common.Hash{}) {
|
||||||
|
posts = s.nodeSet.accountNodes
|
||||||
|
} else {
|
||||||
|
posts = s.nodeSet.storageNodes[owner]
|
||||||
|
}
|
||||||
|
nodes[owner] = make(map[string][]byte)
|
||||||
|
|
||||||
|
for path, oldvalue := range origins {
|
||||||
|
n, exists := posts[path]
|
||||||
|
if !exists {
|
||||||
|
// something not expected
|
||||||
|
return nil, fmt.Errorf("node with origin is not found, %x-%v", owner, []byte(path))
|
||||||
|
}
|
||||||
|
encodeFull := encodeFullValue(owner, path)
|
||||||
|
if !encodeFull {
|
||||||
|
// Partial encoding is required, try to find the node diffs and
|
||||||
|
// fallback to the full-value encoding if fails.
|
||||||
|
//
|
||||||
|
// The partial encoding will be failed in these certain cases:
|
||||||
|
// - the node is deleted or was not-existent;
|
||||||
|
// - the node type has been changed (e.g, from short to full)
|
||||||
|
nElem, indices, diffs, err := trie.NodeDifference(oldvalue, n.Blob)
|
||||||
|
if err != nil {
|
||||||
|
encodeFull = true // fallback to the full node encoding
|
||||||
|
} else {
|
||||||
|
// Encode the node difference as the history element
|
||||||
|
blob := encodeNodeCompressed(nElem == 2, diffs, indices)
|
||||||
|
nodes[owner][path] = blob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if encodeFull {
|
||||||
|
// Encode the entire original value as the history element
|
||||||
|
nodes[owner][path] = encodeNodeFull(oldvalue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
)
|
)
|
||||||
|
|
@ -126,3 +128,49 @@ func TestNodeSetWithOriginEncode(t *testing.T) {
|
||||||
t.Fatalf("Unexpected data size, got: %d, want: %d", dec2.size, s.size)
|
t.Fatalf("Unexpected data size, got: %d, want: %d", dec2.size, s.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncodeFullNodeCompressed(t *testing.T) {
|
||||||
|
var (
|
||||||
|
elements [][]byte
|
||||||
|
indices []int
|
||||||
|
)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
if rand.Intn(2) == 0 {
|
||||||
|
elements = append(elements, testrand.Bytes(20))
|
||||||
|
indices = append(indices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc := encodeNodeCompressed(true, elements, indices)
|
||||||
|
decElements, decIndices, err := decodeNodeCompressed(enc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode node compressed, %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(elements, decElements) {
|
||||||
|
t.Fatalf("Elements are not matched")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(indices, decIndices) {
|
||||||
|
t.Fatalf("Indices are not matched")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeShortNodeCompressed(t *testing.T) {
|
||||||
|
var (
|
||||||
|
elements [][]byte
|
||||||
|
indices []int
|
||||||
|
)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
elements = append(elements, testrand.Bytes(20))
|
||||||
|
indices = append(indices, i)
|
||||||
|
}
|
||||||
|
enc := encodeNodeCompressed(false, elements, indices)
|
||||||
|
decElements, decIndices, err := decodeNodeCompressed(enc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to decode node compressed, %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(elements, decElements) {
|
||||||
|
t.Fatalf("Elements are not matched")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(indices, decIndices) {
|
||||||
|
t.Fatalf("Indices are not matched")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue