Skip to content

Commit 130c52a

Browse files
committed
trie: faster trie node encoding (ethereum#24126)
1 parent 3725e06 commit 130c52a

File tree

8 files changed

+227
-156
lines changed

8 files changed

+227
-156
lines changed

trie/committer.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ type leaf struct {
4444
// By 'some level' of parallelism, it's still the case that all leaves will be
4545
// processed sequentially - onleaf will never be called in parallel or out of order.
4646
type committer struct {
47-
tmp sliceBuffer
4847
sha crypto.KeccakState
4948

5049
onleaf LeafCallback
@@ -55,7 +54,6 @@ type committer struct {
5554
var committerPool = sync.Pool{
5655
New: func() interface{} {
5756
return &committer{
58-
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full FullNode.
5957
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
6058
}
6159
},

trie/database.go

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,9 @@ func (n rawFullNode) cache() (hashNode, bool) { panic("this should never end u
112112
func (n rawFullNode) fstring(ind string) string { panic("this should never end up in a live trie") }
113113

114114
func (n rawFullNode) EncodeRLP(w io.Writer) error {
115-
var nodes [17]node
116-
117-
for i, child := range n {
118-
if child != nil {
119-
nodes[i] = child
120-
} else {
121-
nodes[i] = nilValueNode
122-
}
123-
}
124-
return rlp.Encode(w, nodes)
115+
eb := rlp.NewEncoderBuffer(w)
116+
n.encode(eb)
117+
return eb.Flush()
125118
}
126119

127120
// rawShortNode represents only the useful data content of a short Node, with the
@@ -163,11 +156,7 @@ func (n *cachedNode) rlp() []byte {
163156
if node, ok := n.node.(rawNode); ok {
164157
return node
165158
}
166-
blob, err := rlp.EncodeToBytes(n.node)
167-
if err != nil {
168-
panic(err)
169-
}
170-
return blob
159+
return nodeToBytes(n.node)
171160
}
172161

173162
// obj returns the decoded and expanded trie Node, either directly from the Cache,

trie/hasher.go

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,22 @@ import (
2424
"golang.org/x/crypto/sha3"
2525
)
2626

27-
type sliceBuffer []byte
28-
29-
func (b *sliceBuffer) Write(data []byte) (n int, err error) {
30-
*b = append(*b, data...)
31-
return len(data), nil
32-
}
33-
34-
func (b *sliceBuffer) Reset() {
35-
*b = (*b)[:0]
36-
}
37-
3827
// hasher is a type used for the trie Hash operation. A hasher has some
3928
// internal preallocated temp space
4029
type hasher struct {
4130
sha crypto.KeccakState
42-
tmp sliceBuffer
31+
tmp []byte
32+
encbuf rlp.EncoderBuffer
4333
parallel bool // Whether to use paralallel threads when hashing
4434
}
4535

4636
// hasherPool holds pureHashers
4737
var hasherPool = sync.Pool{
4838
New: func() interface{} {
4939
return &hasher{
50-
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full FullNode.
51-
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
40+
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
41+
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
42+
encbuf: rlp.NewEncoderBuffer(nil),
5243
}
5344
},
5445
}
@@ -153,30 +144,41 @@ func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached
153144
// into compact form for RLP encoding.
154145
// If the rlp data is smaller than 32 bytes, `nil` is returned.
155146
func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
156-
h.tmp.Reset()
157-
if err := rlp.Encode(&h.tmp, n); err != nil {
158-
panic("encode error: " + err.Error())
159-
}
147+
n.encode(h.encbuf)
148+
enc := h.encodedBytes()
160149

161-
if len(h.tmp) < 32 && !force {
150+
if len(enc) < 32 && !force {
162151
return n // Nodes smaller than 32 bytes are stored inside their parent
163152
}
164-
return h.hashData(h.tmp)
153+
return h.hashData(enc)
165154
}
166155

167156
// shortnodeToHash is used to creates a HashNode from a set of hashNodes, (which
168157
// may contain nil values)
169158
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
170-
h.tmp.Reset()
171-
// Generate the RLP encoding of the Node
172-
if err := n.EncodeRLP(&h.tmp); err != nil {
173-
panic("encode error: " + err.Error())
174-
}
159+
n.encode(h.encbuf)
160+
enc := h.encodedBytes()
175161

176-
if len(h.tmp) < 32 && !force {
162+
if len(enc) < 32 && !force {
177163
return n // Nodes smaller than 32 bytes are stored inside their parent
178164
}
179-
return h.hashData(h.tmp)
165+
return h.hashData(enc)
166+
}
167+
168+
// encodedBytes returns the result of the last encoding operation on h.encbuf.
169+
// This also resets the encoder buffer.
170+
//
171+
// All node encoding must be done like this:
172+
//
173+
// node.encode(h.encbuf)
174+
// enc := h.encodedBytes()
175+
//
176+
// This convention exists because node.encode can only be inlined/escape-analyzed when
177+
// called on a concrete receiver type.
178+
func (h *hasher) encodedBytes() []byte {
179+
h.tmp = h.encbuf.AppendToBytes(h.tmp[:0])
180+
h.encbuf.Reset(nil)
181+
return h.tmp
180182
}
181183

182184
// hashData hashes the provided data

trie/iterator.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"errors"
2323

2424
"github.com/XinFinOrg/XDPoSChain/common"
25-
"github.com/XinFinOrg/XDPoSChain/rlp"
2625
)
2726

2827
// Iterator is a key-value trie iterator that traverses a Trie.
@@ -190,8 +189,7 @@ func (it *nodeIterator) LeafProof() [][]byte {
190189
// Gather nodes that end up as hash nodes (or the root)
191190
node, hashed := hasher.proofHash(item.node)
192191
if _, ok := hashed.(hashNode); ok || i == 0 {
193-
enc, _ := rlp.EncodeToBytes(node)
194-
proofs = append(proofs, enc)
192+
proofs = append(proofs, nodeToBytes(node))
195193
}
196194
}
197195
return proofs

trie/node.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import (
2828
var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"}
2929

3030
type node interface {
31-
fstring(string) string
3231
cache() (hashNode, bool)
32+
encode(w rlp.EncoderBuffer)
33+
fstring(string) string
3334
}
3435

3536
type (
@@ -52,16 +53,9 @@ var nilValueNode = valueNode(nil)
5253

5354
// EncodeRLP encodes a full Node into the consensus RLP format.
5455
func (n *fullNode) EncodeRLP(w io.Writer) error {
55-
var nodes [17]node
56-
57-
for i, child := range &n.Children {
58-
if child != nil {
59-
nodes[i] = child
60-
} else {
61-
nodes[i] = nilValueNode
62-
}
63-
}
64-
return rlp.Encode(w, nodes)
56+
eb := rlp.NewEncoderBuffer(w)
57+
n.encode(eb)
58+
return eb.Flush()
6559
}
6660

6761
func (n *fullNode) copy() *fullNode { copy := *n; return &copy }

trie/node_enc.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 trie
18+
19+
import (
20+
"github.com/XinFinOrg/XDPoSChain/rlp"
21+
)
22+
23+
func nodeToBytes(n node) []byte {
24+
w := rlp.NewEncoderBuffer(nil)
25+
n.encode(w)
26+
result := w.ToBytes()
27+
w.Flush()
28+
return result
29+
}
30+
31+
func (n *fullNode) encode(w rlp.EncoderBuffer) {
32+
offset := w.List()
33+
for _, c := range n.Children {
34+
if c != nil {
35+
c.encode(w)
36+
} else {
37+
w.Write(rlp.EmptyString)
38+
}
39+
}
40+
w.ListEnd(offset)
41+
}
42+
43+
func (n *shortNode) encode(w rlp.EncoderBuffer) {
44+
offset := w.List()
45+
w.WriteBytes(n.Key)
46+
if n.Val != nil {
47+
n.Val.encode(w)
48+
} else {
49+
w.Write(rlp.EmptyString)
50+
}
51+
w.ListEnd(offset)
52+
}
53+
54+
func (n hashNode) encode(w rlp.EncoderBuffer) {
55+
w.WriteBytes(n)
56+
}
57+
58+
func (n valueNode) encode(w rlp.EncoderBuffer) {
59+
w.WriteBytes(n)
60+
}
61+
62+
func (n rawFullNode) encode(w rlp.EncoderBuffer) {
63+
offset := w.List()
64+
for _, c := range n {
65+
if c != nil {
66+
c.encode(w)
67+
} else {
68+
w.Write(rlp.EmptyString)
69+
}
70+
}
71+
w.ListEnd(offset)
72+
}
73+
74+
func (n *rawShortNode) encode(w rlp.EncoderBuffer) {
75+
offset := w.List()
76+
w.WriteBytes(n.Key)
77+
if n.Val != nil {
78+
n.Val.encode(w)
79+
} else {
80+
w.Write(rlp.EmptyString)
81+
}
82+
w.ListEnd(offset)
83+
}
84+
85+
func (n rawNode) encode(w rlp.EncoderBuffer) {
86+
w.Write(n)
87+
}

trie/proof.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"github.com/XinFinOrg/XDPoSChain/ethdb"
2626
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
2727
"github.com/XinFinOrg/XDPoSChain/log"
28-
"github.com/XinFinOrg/XDPoSChain/rlp"
2928
)
3029

3130
// Prove constructs a merkle proof for key. The result contains all encoded nodes
@@ -79,7 +78,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
7978
if hash, ok := hn.(hashNode); ok || i == 0 {
8079
// If the Node's database encoding is a hash (or is the
8180
// root Node), it becomes a proof element.
82-
enc, _ := rlp.EncodeToBytes(n)
81+
enc := nodeToBytes(n)
8382
if !ok {
8483
hash = hasher.hashData(enc)
8584
}

0 commit comments

Comments
 (0)