mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-25 21:17:16 +00:00
460 lines
12 KiB
Go
460 lines
12 KiB
Go
package trie
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/ledgerwatch/turbo-geth/common"
|
|
"github.com/ledgerwatch/turbo-geth/common/dbutils"
|
|
"github.com/ledgerwatch/turbo-geth/core/types/accounts"
|
|
"github.com/ledgerwatch/turbo-geth/crypto"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func genAccount() *accounts.Account {
|
|
acc := &accounts.Account{}
|
|
acc.Nonce = 123
|
|
return acc
|
|
}
|
|
|
|
func genNKeys(n int) [][]byte {
|
|
result := make([][]byte, n)
|
|
for i := range result {
|
|
result[i] = crypto.Keccak256([]byte{0x0, 0x0, 0x0, byte(i % 256), byte(i / 256)})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func genByteArrayOfLen(n int) []byte {
|
|
result := make([]byte, n)
|
|
for i := range result {
|
|
result[i] = byte(rand.Intn(255))
|
|
}
|
|
return result
|
|
}
|
|
|
|
type partialObserver struct {
|
|
NoopObserver
|
|
callbackCalled bool
|
|
}
|
|
|
|
func (o *partialObserver) BranchNodeDeleted(_ []byte) {
|
|
o.callbackCalled = true
|
|
}
|
|
|
|
type mockObserver struct {
|
|
createdNodes map[string]uint
|
|
deletedNodes map[string]struct{}
|
|
touchedNodes map[string]int
|
|
|
|
unloadedNodes map[string]int
|
|
unloadedNodeHashes map[string][]byte
|
|
reloadedNodes map[string]int
|
|
}
|
|
|
|
func newMockObserver() *mockObserver {
|
|
return &mockObserver{
|
|
createdNodes: make(map[string]uint),
|
|
deletedNodes: make(map[string]struct{}),
|
|
touchedNodes: make(map[string]int),
|
|
unloadedNodes: make(map[string]int),
|
|
unloadedNodeHashes: make(map[string][]byte),
|
|
reloadedNodes: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
func (m *mockObserver) BranchNodeCreated(hex []byte) {
|
|
m.createdNodes[common.Bytes2Hex(hex)] = 1
|
|
}
|
|
|
|
func (m *mockObserver) CodeNodeCreated(hex []byte, size uint) {
|
|
m.createdNodes[common.Bytes2Hex(hex)] = size
|
|
}
|
|
|
|
func (m *mockObserver) BranchNodeDeleted(hex []byte) {
|
|
m.deletedNodes[common.Bytes2Hex(hex)] = struct{}{}
|
|
}
|
|
|
|
func (m *mockObserver) CodeNodeDeleted(hex []byte) {
|
|
m.deletedNodes[common.Bytes2Hex(hex)] = struct{}{}
|
|
}
|
|
|
|
func (m *mockObserver) BranchNodeTouched(hex []byte) {
|
|
value := m.touchedNodes[common.Bytes2Hex(hex)]
|
|
value++
|
|
m.touchedNodes[common.Bytes2Hex(hex)] = value
|
|
}
|
|
|
|
func (m *mockObserver) CodeNodeTouched(hex []byte) {
|
|
value := m.touchedNodes[common.Bytes2Hex(hex)]
|
|
value++
|
|
m.touchedNodes[common.Bytes2Hex(hex)] = value
|
|
}
|
|
|
|
func (m *mockObserver) CodeNodeSizeChanged(hex []byte, newSize uint) {
|
|
m.createdNodes[common.Bytes2Hex(hex)] = newSize
|
|
}
|
|
|
|
func (m *mockObserver) WillUnloadBranchNode(hex []byte, hash common.Hash, incarnation uint64) {
|
|
}
|
|
|
|
func (m *mockObserver) WillUnloadNode(hex []byte, hash common.Hash) {
|
|
dictKey := common.Bytes2Hex(hex)
|
|
value := m.unloadedNodes[dictKey]
|
|
value++
|
|
m.unloadedNodes[dictKey] = value
|
|
m.unloadedNodeHashes[dictKey] = common.CopyBytes(hash[:])
|
|
}
|
|
|
|
func (m *mockObserver) BranchNodeLoaded(hex []byte, incarnation uint64) {
|
|
dictKey := common.Bytes2Hex(hex)
|
|
value := m.reloadedNodes[dictKey]
|
|
value++
|
|
m.reloadedNodes[dictKey] = value
|
|
}
|
|
|
|
func TestObserversBranchNodesCreateDelete(t *testing.T) {
|
|
trie := newEmpty()
|
|
|
|
observer := newMockObserver()
|
|
|
|
trie.AddObserver(observer)
|
|
|
|
keys := genNKeys(100)
|
|
|
|
for _, key := range keys {
|
|
acc := genAccount()
|
|
code := genByteArrayOfLen(100)
|
|
codeHash := crypto.Keccak256(code)
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
trie.UpdateAccount(key, acc)
|
|
trie.UpdateAccountCode(key, codeNode(code)) //nolint:errcheck
|
|
}
|
|
|
|
expectedNodes := calcSubtreeNodes(trie.root)
|
|
|
|
assert.True(t, expectedNodes >= 100, "should register all code nodes")
|
|
assert.Equal(t, expectedNodes, len(observer.createdNodes), "should register all")
|
|
|
|
for _, key := range keys {
|
|
trie.Delete(key)
|
|
}
|
|
|
|
assert.Equal(t, expectedNodes, len(observer.deletedNodes), "should register all")
|
|
}
|
|
|
|
func TestObserverCodeSizeChanged(t *testing.T) {
|
|
rand.Seed(9999)
|
|
|
|
trie := newEmpty()
|
|
|
|
observer := newMockObserver()
|
|
|
|
trie.AddObserver(observer)
|
|
|
|
keys := genNKeys(10)
|
|
|
|
for _, key := range keys {
|
|
acc := genAccount()
|
|
code := genByteArrayOfLen(100)
|
|
codeHash := crypto.Keccak256(code)
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
trie.UpdateAccount(key, acc)
|
|
trie.UpdateAccountCode(key, codeNode(code)) //nolint:errcheck
|
|
|
|
hex := keybytesToHex(key)
|
|
hex = hex[:len(hex)-1]
|
|
newSize, ok := observer.createdNodes[common.Bytes2Hex(hex)]
|
|
assert.True(t, ok, "account should be registed as created")
|
|
assert.Equal(t, 100, int(newSize), "account size should increase when the account code grows")
|
|
|
|
code2 := genByteArrayOfLen(50)
|
|
codeHash2 := crypto.Keccak256(code2)
|
|
acc.CodeHash = common.BytesToHash(codeHash2)
|
|
trie.UpdateAccount(key, acc)
|
|
trie.UpdateAccountCode(key, codeNode(code2)) //nolint:errcheck
|
|
|
|
newSize2, ok := observer.createdNodes[common.Bytes2Hex(hex)]
|
|
assert.True(t, ok, "account should be registed as created")
|
|
assert.Equal(t, -50, int(newSize2)-int(newSize), "account size should decrease when the account code shrinks")
|
|
}
|
|
}
|
|
|
|
func TestObserverUnloadStorageNodes(t *testing.T) {
|
|
rand.Seed(9999)
|
|
|
|
trie := newEmpty()
|
|
|
|
observer := newMockObserver()
|
|
|
|
trie.AddObserver(observer)
|
|
|
|
key := genNKeys(1)[0]
|
|
|
|
storageKeys := genNKeys(10)
|
|
// group all storage keys into a single fullNode
|
|
for i := range storageKeys {
|
|
storageKeys[i][0] = byte(0)
|
|
storageKeys[i][1] = byte(i)
|
|
}
|
|
|
|
fullKeys := make([][]byte, len(storageKeys))
|
|
for i, storageKey := range storageKeys {
|
|
fullKey := dbutils.GenerateCompositeTrieKey(common.BytesToHash(key), common.BytesToHash(storageKey))
|
|
fullKeys[i] = fullKey
|
|
}
|
|
|
|
acc := genAccount()
|
|
trie.UpdateAccount(key, acc)
|
|
|
|
for i, fullKey := range fullKeys {
|
|
trie.Update(fullKey, []byte(fmt.Sprintf("test-value-%d", i)))
|
|
}
|
|
|
|
rootHash := trie.Hash()
|
|
|
|
// adding nodes doesn't add anything
|
|
assert.Equal(t, 0, len(observer.reloadedNodes), "adding nodes doesn't add anything")
|
|
|
|
// unloading nodes adds to the list
|
|
|
|
hex := keybytesToHex(key)
|
|
hex = hex[:len(hex)-1]
|
|
hex = append(hex, 0x0, 0x0, 0x0)
|
|
trie.EvictNode(hex)
|
|
|
|
newRootHash := trie.Hash()
|
|
assert.Equal(t, rootHash, newRootHash, "root hash shouldn't change")
|
|
|
|
assert.Equal(t, 1, len(observer.unloadedNodes), "should unload one full node")
|
|
|
|
hex = keybytesToHex(key)
|
|
|
|
storageKey := fmt.Sprintf("%s000000", common.Bytes2Hex(hex[:len(hex)-1]))
|
|
assert.Equal(t, 1, observer.unloadedNodes[storageKey], "should unload structure nodes")
|
|
|
|
accNode, ok := trie.getAccount(trie.root, hex, 0)
|
|
assert.True(t, ok, "account should be found")
|
|
|
|
sn, ok := accNode.storage.(*shortNode)
|
|
assert.True(t, ok, "storage should be the shortnode contaning hash")
|
|
|
|
_, ok = sn.Val.(hashNode)
|
|
assert.True(t, ok, "storage should be the shortnode contaning hash")
|
|
}
|
|
|
|
func TestObserverLoadNodes(t *testing.T) {
|
|
rand.Seed(9999)
|
|
|
|
subtrie := newEmpty()
|
|
|
|
observer := newMockObserver()
|
|
|
|
// this test needs a specific trie structure
|
|
// ( full )
|
|
// (full) (duo) (duo)
|
|
// (short)(short)(short) (short)(short) (short)(short)
|
|
// (acc1) (acc2) (acc3) (acc4) (acc5) (acc6) (acc7)
|
|
//
|
|
// to ensure this structure we override prefixes of
|
|
// random account keys with the follwing paths
|
|
|
|
prefixes := [][]byte{
|
|
{0x00, 0x00}, //acc1
|
|
{0x00, 0x02}, //acc2
|
|
{0x00, 0x05}, //acc3
|
|
{0x02, 0x02}, //acc4
|
|
{0x02, 0x05}, //acc5
|
|
{0x0A, 0x00}, //acc6
|
|
{0x0A, 0x03}, //acc7
|
|
}
|
|
|
|
keys := genNKeys(7)
|
|
for i := range keys {
|
|
copy(keys[i][:2], prefixes[i])
|
|
}
|
|
|
|
storageKeys := genNKeys(10)
|
|
// group all storage keys into a single fullNode
|
|
for i := range storageKeys {
|
|
storageKeys[i][0] = byte(0)
|
|
storageKeys[i][1] = byte(i)
|
|
}
|
|
|
|
for _, key := range keys {
|
|
acc := genAccount()
|
|
subtrie.UpdateAccount(key, acc)
|
|
|
|
for i, storageKey := range storageKeys {
|
|
fullKey := dbutils.GenerateCompositeTrieKey(common.BytesToHash(key), common.BytesToHash(storageKey))
|
|
subtrie.Update(fullKey, []byte(fmt.Sprintf("test-value-%d", i)))
|
|
}
|
|
}
|
|
|
|
trie := newEmpty()
|
|
trie.AddObserver(observer)
|
|
|
|
hash := subtrie.Hash()
|
|
//nolint:errcheck
|
|
trie.hook([]byte{}, subtrie.root, hash[:])
|
|
|
|
// fullNode
|
|
assert.Equal(t, 1, observer.reloadedNodes["000000"], "should reload structure nodes")
|
|
// duoNode
|
|
assert.Equal(t, 1, observer.reloadedNodes["000200"], "should reload structure nodes")
|
|
// duoNode
|
|
assert.Equal(t, 1, observer.reloadedNodes["000a00"], "should reload structure nodes")
|
|
// root
|
|
assert.Equal(t, 1, observer.reloadedNodes["00"], "should reload structure nodes")
|
|
|
|
// check storages (should have a single fullNode per account)
|
|
for _, key := range keys {
|
|
hex := keybytesToHex(key)
|
|
storageKey := fmt.Sprintf("%s000000", common.Bytes2Hex(hex[:len(hex)-1]))
|
|
assert.Equal(t, 1, observer.reloadedNodes[storageKey], "should reload structure nodes")
|
|
}
|
|
}
|
|
|
|
func TestObserverTouches(t *testing.T) {
|
|
rand.Seed(9999)
|
|
|
|
trie := newEmpty()
|
|
|
|
observer := newMockObserver()
|
|
|
|
trie.AddObserver(observer)
|
|
|
|
keys := genNKeys(3)
|
|
for i := range keys {
|
|
keys[i][0] = 0x00 // 3 belong to the same branch
|
|
}
|
|
|
|
var acc *accounts.Account
|
|
for _, key := range keys {
|
|
// creation touches the account
|
|
acc = genAccount()
|
|
trie.UpdateAccount(key, acc)
|
|
}
|
|
|
|
key := keys[0]
|
|
branchNodeHex := "0000"
|
|
|
|
assert.Equal(t, uint(1), observer.createdNodes[branchNodeHex], "node is created")
|
|
assert.Equal(t, 1, observer.touchedNodes[branchNodeHex])
|
|
|
|
// updating touches the account
|
|
code := genByteArrayOfLen(100)
|
|
codeHash := crypto.Keccak256(code)
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
trie.UpdateAccount(key, acc)
|
|
assert.Equal(t, 2, observer.touchedNodes[branchNodeHex])
|
|
|
|
// updating code touches the account
|
|
trie.UpdateAccountCode(key, codeNode(code)) //nolint:errcheck
|
|
// 2 touches -- retrieve + updae
|
|
assert.Equal(t, 4, observer.touchedNodes[branchNodeHex])
|
|
|
|
// changing storage touches the account
|
|
storageKey := genNKeys(1)[0]
|
|
fullKey := dbutils.GenerateCompositeTrieKey(common.BytesToHash(key), common.BytesToHash(storageKey))
|
|
|
|
trie.Update(fullKey, []byte("value-1"))
|
|
assert.Equal(t, 5, observer.touchedNodes[branchNodeHex])
|
|
|
|
trie.Update(fullKey, []byte("value-2"))
|
|
assert.Equal(t, 6, observer.touchedNodes[branchNodeHex])
|
|
|
|
// getting storage touches the account
|
|
_, ok := trie.Get(fullKey)
|
|
assert.True(t, ok, "should be able to receive storage")
|
|
assert.Equal(t, 7, observer.touchedNodes[branchNodeHex])
|
|
|
|
// deleting storage touches the account
|
|
trie.Delete(fullKey)
|
|
assert.Equal(t, 8, observer.touchedNodes[branchNodeHex])
|
|
|
|
// getting code touches the account
|
|
_, ok = trie.GetAccountCode(key)
|
|
assert.True(t, ok, "should be able to receive code")
|
|
assert.Equal(t, 9, observer.touchedNodes[branchNodeHex])
|
|
|
|
// getting account touches the account
|
|
_, ok = trie.GetAccount(key)
|
|
assert.True(t, ok, "should be able to receive account")
|
|
assert.Equal(t, 10, observer.touchedNodes[branchNodeHex])
|
|
}
|
|
|
|
func TestObserverMux(t *testing.T) {
|
|
trie := newEmpty()
|
|
|
|
observer1 := newMockObserver()
|
|
observer2 := newMockObserver()
|
|
mux := NewTrieObserverMux()
|
|
mux.AddChild(observer1)
|
|
mux.AddChild(observer2)
|
|
|
|
trie.AddObserver(mux)
|
|
|
|
keys := genNKeys(100)
|
|
for _, key := range keys {
|
|
acc := genAccount()
|
|
trie.UpdateAccount(key, acc)
|
|
|
|
code := genByteArrayOfLen(100)
|
|
codeHash := crypto.Keccak256(code)
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
trie.UpdateAccount(key, acc)
|
|
trie.UpdateAccountCode(key, codeNode(code)) //nolint:errcheck
|
|
|
|
_, ok := trie.GetAccount(key)
|
|
assert.True(t, ok, "acount should be found")
|
|
|
|
}
|
|
|
|
trie.Hash()
|
|
|
|
for i, key := range keys {
|
|
if i < 80 {
|
|
trie.Delete(key)
|
|
} else {
|
|
hex := keybytesToHex(key)
|
|
hex = hex[:len(hex)-1]
|
|
trie.EvictNode(CodeKeyFromAddrHash(hex))
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, observer1.createdNodes, observer2.createdNodes, "should propagate created events")
|
|
assert.Equal(t, observer1.deletedNodes, observer2.deletedNodes, "should propagate deleted events")
|
|
assert.Equal(t, observer1.touchedNodes, observer2.touchedNodes, "should propagate touched events")
|
|
|
|
assert.Equal(t, observer1.unloadedNodes, observer2.unloadedNodes, "should propagage unloads")
|
|
assert.Equal(t, observer1.unloadedNodeHashes, observer2.unloadedNodeHashes, "should propagage unloads")
|
|
assert.Equal(t, observer1.reloadedNodes, observer2.reloadedNodes, "should propagage reloads")
|
|
}
|
|
|
|
func TestObserverPartial(t *testing.T) {
|
|
trie := newEmpty()
|
|
|
|
observer := &partialObserver{callbackCalled: false} // only implements `BranchNodeDeleted`
|
|
trie.AddObserver(observer)
|
|
|
|
keys := genNKeys(2)
|
|
for _, key := range keys {
|
|
acc := genAccount()
|
|
trie.UpdateAccount(key, acc)
|
|
|
|
code := genByteArrayOfLen(100)
|
|
codeHash := crypto.Keccak256(code)
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
trie.UpdateAccount(key, acc)
|
|
trie.UpdateAccountCode(key, codeNode(code)) //nolint:errcheck
|
|
|
|
}
|
|
for _, key := range keys {
|
|
trie.Delete(key)
|
|
}
|
|
|
|
assert.True(t, observer.callbackCalled, "should be called")
|
|
}
|