mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
trie: optimize node encoding using in-place RLP encoder.
This commit is contained in:
parent
c053eb71b6
commit
17d0291609
10 changed files with 59 additions and 76 deletions
1
go.mod
1
go.mod
|
|
@ -53,6 +53,7 @@ require (
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
||||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7
|
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7
|
||||||
|
github.com/qianbin/drlp v0.0.0-20231230065804-db474f66bf91
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
|
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
|
||||||
github.com/status-im/keycard-go v0.2.0
|
github.com/status-im/keycard-go v0.2.0
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -522,6 +522,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
|
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
|
||||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
|
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
|
||||||
|
github.com/qianbin/drlp v0.0.0-20231230065804-db474f66bf91 h1:N1pcRULtPB7CndtexGDr94ai8wsJLx55AKYfo7F6qEs=
|
||||||
|
github.com/qianbin/drlp v0.0.0-20231230065804-db474f66bf91/go.mod h1:OnClEjurpFUtR3RUCauP9HxNNl8xjfGAOv0kWYTznOc=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ func (c *committer) store(path []byte, n node) node {
|
||||||
}
|
}
|
||||||
// Collect the dirty node to nodeset for return.
|
// Collect the dirty node to nodeset for return.
|
||||||
nhash := common.BytesToHash(hash)
|
nhash := common.BytesToHash(hash)
|
||||||
c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n)))
|
c.nodes.AddNode(path, trienode.New(nhash, n.encode(nil)))
|
||||||
|
|
||||||
// Collect the corresponding leaf node if it's required. We don't check
|
// Collect the corresponding leaf node if it's required. We don't check
|
||||||
// full node since it's impossible to store value in fullNode. The key
|
// full node since it's impossible to store value in fullNode. The key
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,7 +28,6 @@ import (
|
||||||
type hasher struct {
|
type hasher struct {
|
||||||
sha crypto.KeccakState
|
sha crypto.KeccakState
|
||||||
tmp []byte
|
tmp []byte
|
||||||
encbuf rlp.EncoderBuffer
|
|
||||||
parallel bool // Whether to use parallel threads when hashing
|
parallel bool // Whether to use parallel threads when hashing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,9 +35,8 @@ type hasher struct {
|
||||||
var hasherPool = sync.Pool{
|
var hasherPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return &hasher{
|
return &hasher{
|
||||||
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
|
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
|
||||||
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
|
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
|
||||||
encbuf: rlp.NewEncoderBuffer(nil),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -143,41 +140,23 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
|
||||||
// into compact form for RLP encoding.
|
// into compact form for RLP encoding.
|
||||||
// If the rlp data is smaller than 32 bytes, `nil` is returned.
|
// If the rlp data is smaller than 32 bytes, `nil` is returned.
|
||||||
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
||||||
n.encode(h.encbuf)
|
h.tmp = n.encode(h.tmp[:0])
|
||||||
enc := h.encodedBytes()
|
|
||||||
|
|
||||||
if len(enc) < 32 && !force {
|
if len(h.tmp) < 32 && !force {
|
||||||
return n // Nodes smaller than 32 bytes are stored inside their parent
|
return n // Nodes smaller than 32 bytes are stored inside their parent
|
||||||
}
|
}
|
||||||
return h.hashData(enc)
|
return h.hashData(h.tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullnodeToHash is used to create a hashNode from a fullNode, (which
|
// fullnodeToHash is used to create a hashNode from a fullNode, (which
|
||||||
// may contain nil values)
|
// may contain nil values)
|
||||||
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
||||||
n.encode(h.encbuf)
|
h.tmp = n.encode(h.tmp[:0])
|
||||||
enc := h.encodedBytes()
|
|
||||||
|
|
||||||
if len(enc) < 32 && !force {
|
if len(h.tmp) < 32 && !force {
|
||||||
return n // Nodes smaller than 32 bytes are stored inside their parent
|
return n // Nodes smaller than 32 bytes are stored inside their parent
|
||||||
}
|
}
|
||||||
return h.hashData(enc)
|
return h.hashData(h.tmp)
|
||||||
}
|
|
||||||
|
|
||||||
// encodedBytes returns the result of the last encoding operation on h.encbuf.
|
|
||||||
// This also resets the encoder buffer.
|
|
||||||
//
|
|
||||||
// All node encoding must be done like this:
|
|
||||||
//
|
|
||||||
// node.encode(h.encbuf)
|
|
||||||
// enc := h.encodedBytes()
|
|
||||||
//
|
|
||||||
// This convention exists because node.encode can only be inlined/escape-analyzed when
|
|
||||||
// called on a concrete receiver type.
|
|
||||||
func (h *hasher) encodedBytes() []byte {
|
|
||||||
h.tmp = h.encbuf.AppendToBytes(h.tmp[:0])
|
|
||||||
h.encbuf.Reset(nil)
|
|
||||||
return h.tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashData hashes the provided data
|
// hashData hashes the provided data
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ func (it *nodeIterator) LeafProof() [][]byte {
|
||||||
// Gather nodes that end up as hash nodes (or the root)
|
// Gather nodes that end up as hash nodes (or the root)
|
||||||
node, hashed := hasher.proofHash(item.node)
|
node, hashed := hasher.proofHash(item.node)
|
||||||
if _, ok := hashed.(hashNode); ok || i == 0 {
|
if _, ok := hashed.(hashNode); ok || i == 0 {
|
||||||
proofs = append(proofs, nodeToBytes(node))
|
proofs = append(proofs, node.encode(nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return proofs
|
return proofs
|
||||||
|
|
|
||||||
11
trie/node.go
11
trie/node.go
|
|
@ -29,7 +29,8 @@ var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b
|
||||||
|
|
||||||
type node interface {
|
type node interface {
|
||||||
cache() (hashNode, bool)
|
cache() (hashNode, bool)
|
||||||
encode(w rlp.EncoderBuffer)
|
// encode appends encoded bytes to buf and returns then extended buffer.
|
||||||
|
encode(buf []byte) []byte
|
||||||
fstring(string) string
|
fstring(string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,9 +54,11 @@ var nilValueNode = valueNode(nil)
|
||||||
|
|
||||||
// EncodeRLP encodes a full node into the consensus RLP format.
|
// EncodeRLP encodes a full node into the consensus RLP format.
|
||||||
func (n *fullNode) EncodeRLP(w io.Writer) error {
|
func (n *fullNode) EncodeRLP(w io.Writer) error {
|
||||||
eb := rlp.NewEncoderBuffer(w)
|
h := newHasher(false)
|
||||||
n.encode(eb)
|
defer returnHasherToPool(h)
|
||||||
return eb.Flush()
|
h.tmp = n.encode(h.tmp[:0])
|
||||||
|
_, err := w.Write(h.tmp)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
||||||
|
|
|
||||||
|
|
@ -17,48 +17,46 @@
|
||||||
package trie
|
package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/qianbin/drlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func nodeToBytes(n node) []byte {
|
func (n *fullNode) encode(buf []byte) []byte {
|
||||||
w := rlp.NewEncoderBuffer(nil)
|
if buf == nil {
|
||||||
n.encode(w)
|
buf = make([]byte, 0, 550)
|
||||||
result := w.ToBytes()
|
}
|
||||||
w.Flush()
|
offset := len(buf)
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fullNode) encode(w rlp.EncoderBuffer) {
|
|
||||||
offset := w.List()
|
|
||||||
for _, c := range n.Children {
|
for _, c := range n.Children {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
c.encode(w)
|
buf = c.encode(buf)
|
||||||
} else {
|
} else {
|
||||||
w.Write(rlp.EmptyString)
|
buf = drlp.AppendUint(buf, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.ListEnd(offset)
|
return drlp.EndList(buf, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *shortNode) encode(w rlp.EncoderBuffer) {
|
func (n *shortNode) encode(buf []byte) []byte {
|
||||||
offset := w.List()
|
if buf == nil {
|
||||||
w.WriteBytes(n.Key)
|
buf = make([]byte, 0, len(n.Key)+40)
|
||||||
if n.Val != nil {
|
|
||||||
n.Val.encode(w)
|
|
||||||
} else {
|
|
||||||
w.Write(rlp.EmptyString)
|
|
||||||
}
|
}
|
||||||
w.ListEnd(offset)
|
offset := len(buf)
|
||||||
|
buf = drlp.AppendString(buf, n.Key)
|
||||||
|
if n.Val != nil {
|
||||||
|
buf = n.Val.encode(buf)
|
||||||
|
} else {
|
||||||
|
buf = drlp.AppendUint(buf, 0)
|
||||||
|
}
|
||||||
|
return drlp.EndList(buf, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n hashNode) encode(w rlp.EncoderBuffer) {
|
func (n hashNode) encode(buf []byte) []byte {
|
||||||
w.WriteBytes(n)
|
return drlp.AppendString(buf, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n valueNode) encode(w rlp.EncoderBuffer) {
|
func (n valueNode) encode(buf []byte) []byte {
|
||||||
w.WriteBytes(n)
|
return drlp.AppendString(buf, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n rawNode) encode(w rlp.EncoderBuffer) {
|
func (n rawNode) encode(buf []byte) []byte {
|
||||||
w.Write(n)
|
return append(buf, n...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ func BenchmarkEncodeShortNode(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
nodeToBytes(node)
|
node.encode(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ func BenchmarkEncodeFullNode(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
nodeToBytes(node)
|
node.encode(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ func BenchmarkDecodeShortNode(b *testing.B) {
|
||||||
Key: []byte{0x1, 0x2},
|
Key: []byte{0x1, 0x2},
|
||||||
Val: hashNode(randBytes(32)),
|
Val: hashNode(randBytes(32)),
|
||||||
}
|
}
|
||||||
blob := nodeToBytes(node)
|
blob := node.encode(nil)
|
||||||
hash := crypto.Keccak256(blob)
|
hash := crypto.Keccak256(blob)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -161,7 +161,7 @@ func BenchmarkDecodeShortNodeUnsafe(b *testing.B) {
|
||||||
Key: []byte{0x1, 0x2},
|
Key: []byte{0x1, 0x2},
|
||||||
Val: hashNode(randBytes(32)),
|
Val: hashNode(randBytes(32)),
|
||||||
}
|
}
|
||||||
blob := nodeToBytes(node)
|
blob := node.encode(nil)
|
||||||
hash := crypto.Keccak256(blob)
|
hash := crypto.Keccak256(blob)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -182,7 +182,7 @@ func BenchmarkDecodeFullNode(b *testing.B) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
node.Children[i] = hashNode(randBytes(32))
|
node.Children[i] = hashNode(randBytes(32))
|
||||||
}
|
}
|
||||||
blob := nodeToBytes(node)
|
blob := node.encode(nil)
|
||||||
hash := crypto.Keccak256(blob)
|
hash := crypto.Keccak256(blob)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
@ -203,7 +203,7 @@ func BenchmarkDecodeFullNodeUnsafe(b *testing.B) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
node.Children[i] = hashNode(randBytes(32))
|
node.Children[i] = hashNode(randBytes(32))
|
||||||
}
|
}
|
||||||
blob := nodeToBytes(node)
|
blob := node.encode(nil)
|
||||||
hash := crypto.Keccak256(blob)
|
hash := crypto.Keccak256(blob)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
||||||
if hash, ok := hn.(hashNode); ok || i == 0 {
|
if hash, ok := hn.(hashNode); ok || i == 0 {
|
||||||
// If the node's database encoding is a hash (or is the
|
// If the node's database encoding is a hash (or is the
|
||||||
// root node), it becomes a proof element.
|
// root node), it becomes a proof element.
|
||||||
enc := nodeToBytes(n)
|
enc := n.encode(nil)
|
||||||
if !ok {
|
if !ok {
|
||||||
hash = hasher.hashData(enc)
|
hash = hasher.hashData(enc)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,8 +377,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
||||||
st.children[i] = nil
|
st.children[i] = nil
|
||||||
stPool.Put(child.reset()) // Release child back to pool.
|
stPool.Put(child.reset()) // Release child back to pool.
|
||||||
}
|
}
|
||||||
nodes.encode(t.h.encbuf)
|
t.h.tmp = nodes.encode(t.h.tmp[:0])
|
||||||
blob = t.h.encodedBytes()
|
blob = t.h.tmp
|
||||||
|
|
||||||
case extNode:
|
case extNode:
|
||||||
// recursively hash and commit child as the first step
|
// recursively hash and commit child as the first step
|
||||||
|
|
@ -400,8 +400,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
||||||
} else {
|
} else {
|
||||||
n.Val = hashNode(st.children[0].val)
|
n.Val = hashNode(st.children[0].val)
|
||||||
}
|
}
|
||||||
n.encode(t.h.encbuf)
|
t.h.tmp = n.encode(t.h.tmp[:0])
|
||||||
blob = t.h.encodedBytes()
|
blob = t.h.tmp
|
||||||
|
|
||||||
stPool.Put(st.children[0].reset()) // Release child back to pool.
|
stPool.Put(st.children[0].reset()) // Release child back to pool.
|
||||||
st.children[0] = nil
|
st.children[0] = nil
|
||||||
|
|
@ -410,8 +410,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
||||||
st.key = append(st.key, byte(16))
|
st.key = append(st.key, byte(16))
|
||||||
n := shortNode{Key: hexToCompactInPlace(st.key), Val: valueNode(st.val)}
|
n := shortNode{Key: hexToCompactInPlace(st.key), Val: valueNode(st.val)}
|
||||||
|
|
||||||
n.encode(t.h.encbuf)
|
t.h.tmp = n.encode(t.h.tmp[:0])
|
||||||
blob = t.h.encodedBytes()
|
blob = t.h.tmp
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue