Skip to content

Commit 8abe4d7

Browse files
holimangzliudan
authored andcommitted
trie: make stacktrie not mutate input values (ethereum#22673)
The stacktrie is a bit un-untuitive, API-wise: since it mutates input values. Such behaviour is dangerous, and easy to get wrong if the calling code 'forgets' this quirk. The behaviour is fixed by this PR, so that the input values are not modified by the stacktrie. Note: just as with the Trie, the stacktrie still references the live input objects, so it's still _not_ safe to mutate the values form the callsite.
1 parent d6d27df commit 8abe4d7

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed

trie/stacktrie.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,7 @@ func (st *StackTrie) hash() {
347347
panic(err)
348348
}
349349
case emptyNode:
350-
st.val = st.val[:0]
351-
st.val = append(st.val, emptyRoot[:]...)
350+
st.val = emptyRoot.Bytes()
352351
st.key = st.key[:0]
353352
st.nodeType = hashedNode
354353
return
@@ -358,17 +357,12 @@ func (st *StackTrie) hash() {
358357
st.key = st.key[:0]
359358
st.nodeType = hashedNode
360359
if len(h.tmp) < 32 {
361-
st.val = st.val[:0]
362-
st.val = append(st.val, h.tmp...)
360+
st.val = common.CopyBytes(h.tmp)
363361
return
364362
}
365-
// Going to write the hash to the 'val'. Need to ensure it's properly sized first
366-
// Typically, 'branchNode's will have no 'val', and require this allocation
367-
if required := 32 - len(st.val); required > 0 {
368-
buf := make([]byte, required)
369-
st.val = append(st.val, buf...)
370-
}
371-
st.val = st.val[:32]
363+
// Write the hash to the 'val'. We allocate a new val here to not mutate
364+
// input values
365+
st.val = make([]byte, 32)
372366
h.sha.Reset()
373367
h.sha.Write(h.tmp)
374368
h.sha.Read(st.val)

trie/stacktrie_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package trie
22

33
import (
4+
"bytes"
5+
"math/big"
46
"testing"
57

68
"github.com/XinFinOrg/XDPoSChain/common"
9+
"github.com/XinFinOrg/XDPoSChain/crypto"
710
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
811
)
912

@@ -119,3 +122,51 @@ func TestUpdateVariableKeys(t *testing.T) {
119122
t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
120123
}
121124
}
125+
126+
// TestStacktrieNotModifyValues checks that inserting blobs of data into the
127+
// stacktrie does not mutate the blobs
128+
func TestStacktrieNotModifyValues(t *testing.T) {
129+
st := NewStackTrie(nil)
130+
{ // Test a very small trie
131+
// Give it the value as a slice with large backing alloc,
132+
// so if the stacktrie tries to append, it won't have to realloc
133+
value := make([]byte, 1, 100)
134+
value[0] = 0x2
135+
want := common.CopyBytes(value)
136+
st.TryUpdate([]byte{0x01}, value)
137+
st.Hash()
138+
if have := value; !bytes.Equal(have, want) {
139+
t.Fatalf("tiny trie: have %#x want %#x", have, want)
140+
}
141+
st = NewStackTrie(nil)
142+
}
143+
// Test with a larger trie
144+
keyB := big.NewInt(1)
145+
keyDelta := big.NewInt(1)
146+
var vals [][]byte
147+
getValue := func(i int) []byte {
148+
if i%2 == 0 { // large
149+
return crypto.Keccak256(big.NewInt(int64(i)).Bytes())
150+
} else { //small
151+
return big.NewInt(int64(i)).Bytes()
152+
}
153+
}
154+
155+
for i := 0; i < 1000; i++ {
156+
key := common.BigToHash(keyB)
157+
value := getValue(i)
158+
st.TryUpdate(key.Bytes(), value)
159+
vals = append(vals, value)
160+
keyB = keyB.Add(keyB, keyDelta)
161+
keyDelta.Add(keyDelta, common.Big1)
162+
}
163+
st.Hash()
164+
for i := 0; i < 1000; i++ {
165+
want := getValue(i)
166+
167+
have := vals[i]
168+
if !bytes.Equal(have, want) {
169+
t.Fatalf("item %d, have %#x want %#x", i, have, want)
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)