Skip to content

Commit 4f3ba67

Browse files
authored
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 65689e7 commit 4f3ba67

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
@@ -346,8 +346,7 @@ func (st *StackTrie) hash() {
346346
panic(err)
347347
}
348348
case emptyNode:
349-
st.val = st.val[:0]
350-
st.val = append(st.val, emptyRoot[:]...)
349+
st.val = emptyRoot.Bytes()
351350
st.key = st.key[:0]
352351
st.nodeType = hashedNode
353352
return
@@ -357,17 +356,12 @@ func (st *StackTrie) hash() {
357356
st.key = st.key[:0]
358357
st.nodeType = hashedNode
359358
if len(h.tmp) < 32 {
360-
st.val = st.val[:0]
361-
st.val = append(st.val, h.tmp...)
359+
st.val = common.CopyBytes(h.tmp)
362360
return
363361
}
364-
// Going to write the hash to the 'val'. Need to ensure it's properly sized first
365-
// Typically, 'branchNode's will have no 'val', and require this allocation
366-
if required := 32 - len(st.val); required > 0 {
367-
buf := make([]byte, required)
368-
st.val = append(st.val, buf...)
369-
}
370-
st.val = st.val[:32]
362+
// Write the hash to the 'val'. We allocate a new val here to not mutate
363+
// input values
364+
st.val = make([]byte, 32)
371365
h.sha.Reset()
372366
h.sha.Write(h.tmp)
373367
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/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/crypto"
710
"github.com/ethereum/go-ethereum/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)