erigon-pulse/turbo/trie/structural_branch_test.go
Jason Yellick 23d5c7c47f
Fix bugs in trie hash computation (#7337)
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>
2023-04-25 09:25:29 +07:00

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))
}