mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-31 16:21:21 +00:00
23d5c7c47f
There are currently a number of bugs in the flat DB trie hash computation. These bugs are improbable in 'real' scenarios (hence the ability to sync the assorted large chains), and, the repercussions of encountering them are low (generally re-computing the hash at a later block will succeed). Still, they can cause the process to crash, or deadlock, and a clever adversary could feasibly construct a block or storage update designed to trigger these bugs. (Or, an unlucky block may trigger them inadvertently). Based on the tracing in the code, it seems that some of these bugs may have been witnessed in the wild, but not reliably reproduced (for instance when `maxlen >= len(curr)`). 1. There is an infinite loop that can occur in _nextSiblingOfParentInMem. This occurs in the account path when the c.k[1] entry is 'nil' and the code will continuously retry resolving this entry indefinitely. 2. When the next trie node is deeper in the trie than the previous, but is not a descendent of the previous trie node, then the old trie node is inadvertently left in memory instead of nil-ed. This results in an incorrect hash being computed. 3. When the last trie node being processed is comprised entirely of 'f' nibbles, the 'FirstNotCoveredPrefix' returns an empty byte array, because there is no next nibble sub-tree. This causes keys to inappropriately be reprocessed triggering either an index out of bounds panic or incorrect hash results. 4. When the _nextSiblingInDB path is triggered, if the next nibble subtree contains no trie table entries, then any keys with a prefix in that subtree will be skipped resulting in incorrect hash results. The fuzzing seeds included cover all four of these cases (for both accounts and storage where appropriate). As fuzzing is baked into the native go 1.18 toolchain, running 'go test' (as the Makefile currently does) is adequate to cover these failures. To run additional fuzzing the `-fuzz` flag may be provided as documented in the go test doc. Co-authored-by: Jason Yellick <jason@enya.ai>
157 lines
6.2 KiB
Go
157 lines
6.2 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package trie_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"github.com/ledgerwatch/erigon-lib/kv/memdb"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/ledgerwatch/erigon/common"
|
|
"github.com/ledgerwatch/erigon/eth/integrity"
|
|
"github.com/ledgerwatch/erigon/turbo/trie"
|
|
)
|
|
|
|
func TestIHCursor(t *testing.T) {
|
|
db, tx := memdb.NewTestTx(t)
|
|
require := require.New(t)
|
|
hash := libcommon.HexToHash(fmt.Sprintf("%064d", 0))
|
|
|
|
newV := make([]byte, 0, 1024)
|
|
put := func(ks string, hasState, hasTree, hasHash uint16, hashes []libcommon.Hash) {
|
|
k := common.FromHex(ks)
|
|
integrity.AssertSubset(k, hasTree, hasState)
|
|
integrity.AssertSubset(k, hasHash, hasState)
|
|
_ = tx.Put(kv.TrieOfAccounts, k, common.CopyBytes(trie.MarshalTrieNodeTyped(hasState, hasTree, hasHash, hashes, newV)))
|
|
}
|
|
|
|
put("00", 0b0000000000000010, 0b0000000000000000, 0b0000000000000010, []libcommon.Hash{hash})
|
|
put("01", 0b0000000000000111, 0b0000000000000010, 0b0000000000000111, []libcommon.Hash{hash, hash, hash})
|
|
put("0101", 0b0000000000000111, 0b0000000000000000, 0b0000000000000111, []libcommon.Hash{hash, hash, hash})
|
|
put("02", 0b1000000000000000, 0b0000000000000000, 0b1000000000000000, []libcommon.Hash{hash})
|
|
put("03", 0b0000000000000001, 0b0000000000000001, 0b0000000000000000, []libcommon.Hash{})
|
|
put("030000", 0b0000000000000001, 0b0000000000000000, 0b0000000000000001, []libcommon.Hash{hash})
|
|
put("03000e", 0b0000000000000001, 0b0000000000000001, 0b0000000000000001, []libcommon.Hash{hash})
|
|
put("03000e000000", 0b0000000000000100, 0b0000000000000000, 0b0000000000000100, []libcommon.Hash{hash})
|
|
put("03000e00000e", 0b0000000000000100, 0b0000000000000000, 0b0000000000000100, []libcommon.Hash{hash})
|
|
put("05", 0b0000000000000001, 0b0000000000000001, 0b0000000000000001, []libcommon.Hash{hash})
|
|
put("050001", 0b0000000000000001, 0b0000000000000000, 0b0000000000000001, []libcommon.Hash{hash})
|
|
put("05000f", 0b0000000000000001, 0b0000000000000000, 0b0000000000000001, []libcommon.Hash{hash})
|
|
put("06", 0b0000000000000001, 0b0000000000000000, 0b0000000000000001, []libcommon.Hash{hash})
|
|
|
|
integrity.Trie(db, tx, false, context.Background())
|
|
|
|
cursor, err := tx.Cursor(kv.TrieOfAccounts)
|
|
require.NoError(err)
|
|
rl := trie.NewRetainList(0)
|
|
rl.AddHex(common.FromHex("01"))
|
|
rl.AddHex(common.FromHex("0101"))
|
|
rl.AddHex(common.FromHex("030000"))
|
|
rl.AddHex(common.FromHex("03000e"))
|
|
rl.AddHex(common.FromHex("03000e00"))
|
|
rl.AddHex(common.FromHex("0500"))
|
|
var canUse = func(prefix []byte) (bool, []byte) {
|
|
retain, nextCreated := rl.RetainWithMarker(prefix)
|
|
return !retain, nextCreated
|
|
}
|
|
ih := trie.AccTrie(canUse, func(keyHex []byte, _, _, _ uint16, hashes, rootHash []byte) error {
|
|
return nil
|
|
}, cursor, nil)
|
|
k, _, _, _ := ih.AtPrefix([]byte{})
|
|
require.Equal(common.FromHex("0001"), k)
|
|
require.False(ih.SkipState)
|
|
firstPrefix, done := ih.FirstNotCoveredPrefix()
|
|
require.Equal([]byte{}, firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("0100"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("02"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("010100"), k)
|
|
require.True(ih.SkipState)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("010101"), k)
|
|
require.True(ih.SkipState)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("010102"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("1120"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("0102"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("1130"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("020f"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("13"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("03000000"), k)
|
|
require.True(ih.SkipState)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("03000e00000002"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("3001"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("03000e00000e02"), k)
|
|
require.True(ih.SkipState)
|
|
firstPrefix, done = ih.FirstNotCoveredPrefix()
|
|
require.Equal(common.FromHex("30e00030"), firstPrefix)
|
|
require.False(done)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("05000100"), k)
|
|
require.False(ih.SkipState) // Must be false, in case a key exists starting with nibble 4
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("05000f00"), k)
|
|
require.True(ih.SkipState)
|
|
k, _, _, _ = ih.Next()
|
|
require.Equal(common.FromHex("0600"), k)
|
|
require.True(ih.SkipState)
|
|
k, _, _, _ = ih.Next()
|
|
assert.Nil(t, k)
|
|
|
|
//cursorS := tx.Cursor(dbutils.TrieOfStorage)
|
|
//ihStorage := AccTrie(canUse, cursorS)
|
|
//
|
|
//k, _, _ = ihStorage.SeekToAccount(common.FromHex(acc))
|
|
//require.Equal(common.FromHex(acc+"00"), k)
|
|
//require.True(isDenseSequence(ihStorage.prev, k))
|
|
//k, _, _ = ihStorage.Next()
|
|
//require.Equal(common.FromHex(acc+"02"), k)
|
|
//require.False(isDenseSequence(ihStorage.prev, k))
|
|
//k, _, _ = ihStorage.Next()
|
|
//assert.Nil(t, k)
|
|
//require.False(isDenseSequence(ihStorage.prev, k))
|
|
|
|
}
|