@@ -27,6 +27,7 @@ import (
2727
2828var (
2929 stPool = sync.Pool {New : func () any { return new (stNode ) }}
30+ bPool = newBytesPool (32 , 100 )
3031 _ = types .TrieHasher ((* StackTrie )(nil ))
3132)
3233
@@ -47,6 +48,8 @@ type StackTrie struct {
4748 h * hasher
4849 last []byte
4950 onTrieNode OnTrieNode
51+ kBuf []byte // buf space used for hex-key during insertions
52+ pBuf []byte // buf space used for path during insertions
5053}
5154
5255// NewStackTrie allocates and initializes an empty trie. The committed nodes
@@ -56,6 +59,17 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
5659 root : stPool .Get ().(* stNode ),
5760 h : newHasher (false ),
5861 onTrieNode : onTrieNode ,
62+ kBuf : make ([]byte , 64 ),
63+ pBuf : make ([]byte , 64 ),
64+ }
65+ }
66+
67+ func (t * StackTrie ) grow (key []byte ) {
68+ if cap (t .kBuf ) < 2 * len (key ) {
69+ t .kBuf = make ([]byte , 2 * len (key ))
70+ }
71+ if cap (t .pBuf ) < 2 * len (key ) {
72+ t .pBuf = make ([]byte , 2 * len (key ))
5973 }
6074}
6175
@@ -64,7 +78,8 @@ func (t *StackTrie) Update(key, value []byte) error {
6478 if len (value ) == 0 {
6579 return errors .New ("trying to insert empty (deletion)" )
6680 }
67- k := t .TrieKey (key )
81+ t .grow (key )
82+ k := writeHexKey (t .kBuf , key )
6883 if bytes .Compare (t .last , k ) >= 0 {
6984 return errors .New ("non-ascending key order" )
7085 }
@@ -73,7 +88,7 @@ func (t *StackTrie) Update(key, value []byte) error {
7388 } else {
7489 t .last = append (t .last [:0 ], k ... ) // reuse key slice
7590 }
76- t .insert (t .root , k , value , nil )
91+ t .insert (t .root , k , value , t . pBuf [: 0 ] )
7792 return nil
7893}
7994
@@ -129,6 +144,12 @@ const (
129144)
130145
131146func (n * stNode ) reset () * stNode {
147+ if n .typ == hashedNode {
148+ // On hashnodes, we 'own' the val: it is guaranteed to be not held
149+ // by external caller. Hence, when we arrive here, we can put it back
150+ // into the pool
151+ bPool .Put (n .val )
152+ }
132153 n .key = n .key [:0 ]
133154 n .val = nil
134155 for i := range n .children {
@@ -150,8 +171,12 @@ func (n *stNode) getDiffIndex(key []byte) int {
150171 return len (n .key )
151172}
152173
153- // Helper function to that inserts a (key, value) pair into
154- // the trie.
174+ // Helper function to that inserts a (key, value) pair into the trie.
175+ //
176+ // - The key is not retained by this method, but always copied if needed.
177+ // - The value is retained by this method, as long as the leaf that it represents
178+ // remains unhashed. However: it is never modified.
179+ // - The path is not retained by this method.
155180func (t * StackTrie ) insert (st * stNode , key , value []byte , path []byte ) {
156181 switch st .typ {
157182 case branchNode : /* Branch */
@@ -283,7 +308,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
283308
284309 case emptyNode : /* Empty */
285310 st .typ = leafNode
286- st .key = key
311+ st .key = append ( st . key , key ... ) // deep-copy the key as it's volatile
287312 st .val = value
288313
289314 case hashedNode :
@@ -318,35 +343,33 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
318343 return
319344
320345 case branchNode :
321- var nodes fullNode
346+ var nodes fullnodeEncoder
322347 for i , child := range st .children {
323348 if child == nil {
324- nodes .Children [i ] = nilValueNode
325349 continue
326350 }
327351 t .hash (child , append (path , byte (i )))
352+ nodes .Children [i ] = child .val
353+ }
354+ nodes .encode (t .h .encbuf )
355+ blob = t .h .encodedBytes ()
328356
329- if len (child .val ) < 32 {
330- nodes .Children [i ] = rawNode (child .val )
331- } else {
332- nodes .Children [i ] = hashNode (child .val )
357+ for i , child := range st .children {
358+ if child == nil {
359+ continue
333360 }
334361 st .children [i ] = nil
335362 stPool .Put (child .reset ()) // Release child back to pool.
336363 }
337- nodes .encode (t .h .encbuf )
338- blob = t .h .encodedBytes ()
339364
340365 case extNode :
341366 // recursively hash and commit child as the first step
342367 t .hash (st .children [0 ], append (path , st .key ... ))
343368
344369 // encode the extension node
345- n := shortNode {Key : hexToCompactInPlace (st .key )}
346- if len (st .children [0 ].val ) < 32 {
347- n .Val = rawNode (st .children [0 ].val )
348- } else {
349- n .Val = hashNode (st .children [0 ].val )
370+ n := extNodeEncoder {
371+ Key : hexToCompactInPlace (st .key ),
372+ Val : st .children [0 ].val ,
350373 }
351374 n .encode (t .h .encbuf )
352375 blob = t .h .encodedBytes ()
@@ -356,8 +379,10 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
356379
357380 case leafNode :
358381 st .key = append (st .key , byte (16 ))
359- n := shortNode {Key : hexToCompactInPlace (st .key ), Val : valueNode (st .val )}
360-
382+ n := leafNodeEncoder {
383+ Key : hexToCompactInPlace (st .key ),
384+ Val : st .val ,
385+ }
361386 n .encode (t .h .encbuf )
362387 blob = t .h .encodedBytes ()
363388
@@ -368,15 +393,19 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
368393 st .typ = hashedNode
369394 st .key = st .key [:0 ]
370395
396+ st .val = nil // Release reference to potentially externally held slice.
397+
371398 // Skip committing the non-root node if the size is smaller than 32 bytes
372399 // as tiny nodes are always embedded in their parent except root node.
373400 if len (blob ) < 32 && len (path ) > 0 {
374- st .val = common .CopyBytes (blob )
401+ st .val = bPool .GetWithSize (len (blob ))
402+ copy (st .val , blob )
375403 return
376404 }
377405 // Write the hash to the 'val'. We allocate a new val here to not mutate
378406 // input values.
379- st .val = t .h .hashData (blob )
407+ st .val = bPool .GetWithSize (32 )
408+ t .h .hashDataTo (st .val , blob )
380409
381410 // Invoke the callback it's provided. Notably, the path and blob slices are
382411 // volatile, please deep-copy the slices in callback if the contents need
0 commit comments