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/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
||||
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/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
|
||||
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/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/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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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.
|
||||
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
|
||||
// full node since it's impossible to store value in fullNode. The key
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
|
|
@ -29,7 +28,6 @@ import (
|
|||
type hasher struct {
|
||||
sha crypto.KeccakState
|
||||
tmp []byte
|
||||
encbuf rlp.EncoderBuffer
|
||||
parallel bool // Whether to use parallel threads when hashing
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +37,6 @@ var hasherPool = sync.Pool{
|
|||
return &hasher{
|
||||
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
|
||||
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.
|
||||
// If the rlp data is smaller than 32 bytes, `nil` is returned.
|
||||
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
|
||||
n.encode(h.encbuf)
|
||||
enc := h.encodedBytes()
|
||||
h.tmp = n.encode(h.tmp[:0])
|
||||
|
||||
if len(enc) < 32 && !force {
|
||||
if len(h.tmp) < 32 && !force {
|
||||
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
|
||||
// may contain nil values)
|
||||
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
|
||||
n.encode(h.encbuf)
|
||||
enc := h.encodedBytes()
|
||||
h.tmp = n.encode(h.tmp[:0])
|
||||
|
||||
if len(enc) < 32 && !force {
|
||||
if len(h.tmp) < 32 && !force {
|
||||
return n // Nodes smaller than 32 bytes are stored inside their parent
|
||||
}
|
||||
return h.hashData(enc)
|
||||
}
|
||||
|
||||
// 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
|
||||
return h.hashData(h.tmp)
|
||||
}
|
||||
|
||||
// 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)
|
||||
node, hashed := hasher.proofHash(item.node)
|
||||
if _, ok := hashed.(hashNode); ok || i == 0 {
|
||||
proofs = append(proofs, nodeToBytes(node))
|
||||
proofs = append(proofs, node.encode(nil))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +54,11 @@ var nilValueNode = valueNode(nil)
|
|||
|
||||
// EncodeRLP encodes a full node into the consensus RLP format.
|
||||
func (n *fullNode) EncodeRLP(w io.Writer) error {
|
||||
eb := rlp.NewEncoderBuffer(w)
|
||||
n.encode(eb)
|
||||
return eb.Flush()
|
||||
h := newHasher(false)
|
||||
defer returnHasherToPool(h)
|
||||
h.tmp = n.encode(h.tmp[:0])
|
||||
_, err := w.Write(h.tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
||||
|
|
|
|||
|
|
@ -17,48 +17,46 @@
|
|||
package trie
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/qianbin/drlp"
|
||||
)
|
||||
|
||||
func nodeToBytes(n node) []byte {
|
||||
w := rlp.NewEncoderBuffer(nil)
|
||||
n.encode(w)
|
||||
result := w.ToBytes()
|
||||
w.Flush()
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *fullNode) encode(w rlp.EncoderBuffer) {
|
||||
offset := w.List()
|
||||
func (n *fullNode) encode(buf []byte) []byte {
|
||||
if buf == nil {
|
||||
buf = make([]byte, 0, 550)
|
||||
}
|
||||
offset := len(buf)
|
||||
for _, c := range n.Children {
|
||||
if c != nil {
|
||||
c.encode(w)
|
||||
buf = c.encode(buf)
|
||||
} 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) {
|
||||
offset := w.List()
|
||||
w.WriteBytes(n.Key)
|
||||
func (n *shortNode) encode(buf []byte) []byte {
|
||||
if buf == nil {
|
||||
buf = make([]byte, 0, len(n.Key)+40)
|
||||
}
|
||||
offset := len(buf)
|
||||
buf = drlp.AppendString(buf, n.Key)
|
||||
if n.Val != nil {
|
||||
n.Val.encode(w)
|
||||
buf = n.Val.encode(buf)
|
||||
} else {
|
||||
w.Write(rlp.EmptyString)
|
||||
buf = drlp.AppendUint(buf, 0)
|
||||
}
|
||||
w.ListEnd(offset)
|
||||
return drlp.EndList(buf, offset)
|
||||
}
|
||||
|
||||
func (n hashNode) encode(w rlp.EncoderBuffer) {
|
||||
w.WriteBytes(n)
|
||||
func (n hashNode) encode(buf []byte) []byte {
|
||||
return drlp.AppendString(buf, n)
|
||||
}
|
||||
|
||||
func (n valueNode) encode(w rlp.EncoderBuffer) {
|
||||
w.WriteBytes(n)
|
||||
func (n valueNode) encode(buf []byte) []byte {
|
||||
return drlp.AppendString(buf, n)
|
||||
}
|
||||
|
||||
func (n rawNode) encode(w rlp.EncoderBuffer) {
|
||||
w.Write(n)
|
||||
func (n rawNode) encode(buf []byte) []byte {
|
||||
return append(buf, n...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ func BenchmarkEncodeShortNode(b *testing.B) {
|
|||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
nodeToBytes(node)
|
||||
node.encode(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ func BenchmarkEncodeFullNode(b *testing.B) {
|
|||
b.ReportAllocs()
|
||||
|
||||
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},
|
||||
Val: hashNode(randBytes(32)),
|
||||
}
|
||||
blob := nodeToBytes(node)
|
||||
blob := node.encode(nil)
|
||||
hash := crypto.Keccak256(blob)
|
||||
|
||||
b.ResetTimer()
|
||||
|
|
@ -161,7 +161,7 @@ func BenchmarkDecodeShortNodeUnsafe(b *testing.B) {
|
|||
Key: []byte{0x1, 0x2},
|
||||
Val: hashNode(randBytes(32)),
|
||||
}
|
||||
blob := nodeToBytes(node)
|
||||
blob := node.encode(nil)
|
||||
hash := crypto.Keccak256(blob)
|
||||
|
||||
b.ResetTimer()
|
||||
|
|
@ -182,7 +182,7 @@ func BenchmarkDecodeFullNode(b *testing.B) {
|
|||
for i := 0; i < 16; i++ {
|
||||
node.Children[i] = hashNode(randBytes(32))
|
||||
}
|
||||
blob := nodeToBytes(node)
|
||||
blob := node.encode(nil)
|
||||
hash := crypto.Keccak256(blob)
|
||||
|
||||
b.ResetTimer()
|
||||
|
|
@ -203,7 +203,7 @@ func BenchmarkDecodeFullNodeUnsafe(b *testing.B) {
|
|||
for i := 0; i < 16; i++ {
|
||||
node.Children[i] = hashNode(randBytes(32))
|
||||
}
|
||||
blob := nodeToBytes(node)
|
||||
blob := node.encode(nil)
|
||||
hash := crypto.Keccak256(blob)
|
||||
|
||||
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 the node's database encoding is a hash (or is the
|
||||
// root node), it becomes a proof element.
|
||||
enc := nodeToBytes(n)
|
||||
enc := n.encode(nil)
|
||||
if !ok {
|
||||
hash = hasher.hashData(enc)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,8 +377,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
|||
st.children[i] = nil
|
||||
stPool.Put(child.reset()) // Release child back to pool.
|
||||
}
|
||||
nodes.encode(t.h.encbuf)
|
||||
blob = t.h.encodedBytes()
|
||||
t.h.tmp = nodes.encode(t.h.tmp[:0])
|
||||
blob = t.h.tmp
|
||||
|
||||
case extNode:
|
||||
// recursively hash and commit child as the first step
|
||||
|
|
@ -400,8 +400,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
|||
} else {
|
||||
n.Val = hashNode(st.children[0].val)
|
||||
}
|
||||
n.encode(t.h.encbuf)
|
||||
blob = t.h.encodedBytes()
|
||||
t.h.tmp = n.encode(t.h.tmp[:0])
|
||||
blob = t.h.tmp
|
||||
|
||||
stPool.Put(st.children[0].reset()) // Release child back to pool.
|
||||
st.children[0] = nil
|
||||
|
|
@ -410,8 +410,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
|||
st.key = append(st.key, byte(16))
|
||||
n := shortNode{Key: hexToCompactInPlace(st.key), Val: valueNode(st.val)}
|
||||
|
||||
n.encode(t.h.encbuf)
|
||||
blob = t.h.encodedBytes()
|
||||
t.h.tmp = n.encode(t.h.tmp[:0])
|
||||
blob = t.h.tmp
|
||||
|
||||
default:
|
||||
panic("invalid node type")
|
||||
|
|
|
|||
Loading…
Reference in a new issue