Skip to content

Commit a15d71a

Browse files
authored
core/state/snapshot: stop generator if it hits missing trie nodes (ethereum#21649)
* core/state/snapshot: exit Geth if generator hits missing trie nodes * core/state/snapshot: error instead of hard die on generator fault * core/state/snapshot: don't enable logging on the tests
1 parent 9d1e202 commit a15d71a

File tree

2 files changed

+207
-2
lines changed

2 files changed

+207
-2
lines changed

core/state/snapshot/generate.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
129129
stats.wiping = nil
130130
stats.start = time.Now()
131131

132-
// If generator was aboted during wipe, return
132+
// If generator was aborted during wipe, return
133133
case abort := <-dl.genAbort:
134134
abort <- stats
135135
return
@@ -203,7 +203,10 @@ func (dl *diskLayer) generate(stats *generatorStats) {
203203
if acc.Root != emptyRoot {
204204
storeTrie, err := trie.NewSecure(acc.Root, dl.triedb)
205205
if err != nil {
206-
log.Crit("Storage trie inaccessible for snapshot generation", "err", err)
206+
log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err)
207+
abort := <-dl.genAbort
208+
abort <- stats
209+
return
207210
}
208211
var storeMarker []byte
209212
if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength {
@@ -238,6 +241,12 @@ func (dl *diskLayer) generate(stats *generatorStats) {
238241
}
239242
}
240243
}
244+
if err := storeIt.Err; err != nil {
245+
log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err)
246+
abort := <-dl.genAbort
247+
abort <- stats
248+
return
249+
}
241250
}
242251
if time.Since(logged) > 8*time.Second {
243252
stats.Log("Generating state snapshot", dl.root, accIt.Key)
@@ -246,6 +255,12 @@ func (dl *diskLayer) generate(stats *generatorStats) {
246255
// Some account processed, unmark the marker
247256
accMarker = nil
248257
}
258+
if err := accIt.Err; err != nil {
259+
log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err)
260+
abort := <-dl.genAbort
261+
abort <- stats
262+
return
263+
}
249264
// Snapshot fully generated, set the marker to nil
250265
if batch.ValueSize() > 0 {
251266
batch.Write()

core/state/snapshot/generate_test.go

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2020 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 snapshot
18+
19+
import (
20+
"math/big"
21+
"testing"
22+
"time"
23+
24+
"github.com/ethereum/go-ethereum/common"
25+
"github.com/ethereum/go-ethereum/ethdb/memorydb"
26+
"github.com/ethereum/go-ethereum/rlp"
27+
"github.com/ethereum/go-ethereum/trie"
28+
)
29+
30+
// Tests that snapshot generation errors out correctly in case of a missing trie
31+
// node in the account trie.
32+
func TestGenerateCorruptAccountTrie(t *testing.T) {
33+
// We can't use statedb to make a test trie (circular dependency), so make
34+
// a fake one manually. We're going with a small account trie of 3 accounts,
35+
// without any storage slots to keep the test smaller.
36+
var (
37+
diskdb = memorydb.New()
38+
triedb = trie.NewDatabase(diskdb)
39+
)
40+
tr, _ := trie.NewSecure(common.Hash{}, triedb)
41+
acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
42+
val, _ := rlp.EncodeToBytes(acc)
43+
tr.Update([]byte("acc-1"), val) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
44+
45+
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
46+
val, _ = rlp.EncodeToBytes(acc)
47+
tr.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
48+
49+
acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
50+
val, _ = rlp.EncodeToBytes(acc)
51+
tr.Update([]byte("acc-3"), val) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
52+
tr.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
53+
54+
// Delete an account trie leaf and ensure the generator chokes
55+
triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil)
56+
diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes())
57+
58+
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil)
59+
select {
60+
case <-snap.genPending:
61+
// Snapshot generation succeeded
62+
t.Errorf("Snapshot generated against corrupt account trie")
63+
64+
case <-time.After(250 * time.Millisecond):
65+
// Not generated fast enough, hopefully blocked inside on missing trie node fail
66+
}
67+
// Signal abortion to the generator and wait for it to tear down
68+
stop := make(chan *generatorStats)
69+
snap.genAbort <- stop
70+
<-stop
71+
}
72+
73+
// Tests that snapshot generation errors out correctly in case of a missing root
74+
// trie node for a storage trie. It's similar to internal corruption but it is
75+
// handled differently inside the generator.
76+
func TestGenerateMissingStorageTrie(t *testing.T) {
77+
// We can't use statedb to make a test trie (circular dependency), so make
78+
// a fake one manually. We're going with a small account trie of 3 accounts,
79+
// two of which also has the same 3-slot storage trie attached.
80+
var (
81+
diskdb = memorydb.New()
82+
triedb = trie.NewDatabase(diskdb)
83+
)
84+
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
85+
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
86+
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
87+
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
88+
stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
89+
90+
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
91+
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
92+
val, _ := rlp.EncodeToBytes(acc)
93+
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
94+
95+
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
96+
val, _ = rlp.EncodeToBytes(acc)
97+
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
98+
99+
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
100+
val, _ = rlp.EncodeToBytes(acc)
101+
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
102+
accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
103+
104+
// We can only corrupt the disk database, so flush the tries out
105+
triedb.Reference(
106+
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
107+
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
108+
)
109+
triedb.Reference(
110+
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
111+
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
112+
)
113+
triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
114+
115+
// Delete a storage trie root and ensure the generator chokes
116+
diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes())
117+
118+
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil)
119+
select {
120+
case <-snap.genPending:
121+
// Snapshot generation succeeded
122+
t.Errorf("Snapshot generated against corrupt storage trie")
123+
124+
case <-time.After(250 * time.Millisecond):
125+
// Not generated fast enough, hopefully blocked inside on missing trie node fail
126+
}
127+
// Signal abortion to the generator and wait for it to tear down
128+
stop := make(chan *generatorStats)
129+
snap.genAbort <- stop
130+
<-stop
131+
}
132+
133+
// Tests that snapshot generation errors out correctly in case of a missing trie
134+
// node in a storage trie.
135+
func TestGenerateCorruptStorageTrie(t *testing.T) {
136+
// We can't use statedb to make a test trie (circular dependency), so make
137+
// a fake one manually. We're going with a small account trie of 3 accounts,
138+
// two of which also has the same 3-slot storage trie attached.
139+
var (
140+
diskdb = memorydb.New()
141+
triedb = trie.NewDatabase(diskdb)
142+
)
143+
stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
144+
stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
145+
stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
146+
stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
147+
stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
148+
149+
accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
150+
acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
151+
val, _ := rlp.EncodeToBytes(acc)
152+
accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
153+
154+
acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
155+
val, _ = rlp.EncodeToBytes(acc)
156+
accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
157+
158+
acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
159+
val, _ = rlp.EncodeToBytes(acc)
160+
accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
161+
accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
162+
163+
// We can only corrupt the disk database, so flush the tries out
164+
triedb.Reference(
165+
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
166+
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
167+
)
168+
triedb.Reference(
169+
common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
170+
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
171+
)
172+
triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
173+
174+
// Delete a storage trie leaf and ensure the generator chokes
175+
diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
176+
177+
snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil)
178+
select {
179+
case <-snap.genPending:
180+
// Snapshot generation succeeded
181+
t.Errorf("Snapshot generated against corrupt storage trie")
182+
183+
case <-time.After(250 * time.Millisecond):
184+
// Not generated fast enough, hopefully blocked inside on missing trie node fail
185+
}
186+
// Signal abortion to the generator and wait for it to tear down
187+
stop := make(chan *generatorStats)
188+
snap.genAbort <- stop
189+
<-stop
190+
}

0 commit comments

Comments
 (0)