mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 11:41:19 +00:00
8ea0096d56
This is a non functional change which consolidates the various packages under metrics into the top level package now that the dead code is removed. It is a precursor to the removal of Victoria metrics after which all erigon metrics code will be contained in this single package.
1007 lines
31 KiB
Go
1007 lines
31 KiB
Go
package shards
|
|
|
|
import (
|
|
"bytes"
|
|
"container/heap"
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"github.com/c2h5oh/datasize"
|
|
"github.com/google/btree"
|
|
"github.com/holiman/uint256"
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon/metrics"
|
|
|
|
"github.com/ledgerwatch/erigon/common"
|
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
|
)
|
|
|
|
// LRU state cache consists of two structures - B-Tree and binary heap
|
|
// Every element is marked either as Read, Updated, or Deleted via flags
|
|
|
|
// Metrics
|
|
var (
|
|
AccRead = metrics.GetOrCreateCounter(`cache_total{target="acc_read"}`)
|
|
StRead = metrics.GetOrCreateCounter(`cache_total{target="st_read"}`)
|
|
WritesRead = metrics.GetOrCreateCounter(`cache_total{target="write"}`)
|
|
)
|
|
|
|
const (
|
|
ModifiedFlag uint16 = 1 // Set when the item is different seek what is last committed to the database
|
|
AbsentFlag uint16 = 2 // Set when the item is absent in the state
|
|
DeletedFlag uint16 = 4 // Set when the item is marked for deletion, even though it might have the value in it
|
|
UnprocessedFlag uint16 = 8 // Set when there is a modification in the item that invalidates merkle root calculated previously
|
|
)
|
|
|
|
// Sizes of B-tree items for the purposes of keeping track of the size of reads and writes
|
|
// The sizes of the nodes of the B-tree are not accounted for, because their are private to the `btree` package
|
|
// +16 means - each item has 2 words overhead: 1 for interface, 1 for pointer to item.
|
|
const (
|
|
accountItemSize = int(unsafe.Sizeof(AccountItem{}) + 16)
|
|
accountWriteItemSize = int(unsafe.Sizeof(AccountWriteItem{})+16) + accountItemSize
|
|
storageItemSize = int(unsafe.Sizeof(StorageItem{}) + 16)
|
|
storageWriteItemSize = int(unsafe.Sizeof(StorageWriteItem{})+16) + storageItemSize
|
|
codeItemSize = int(unsafe.Sizeof(CodeItem{}) + 16)
|
|
codeWriteItemSize = int(unsafe.Sizeof(CodeWriteItem{})+16) + codeItemSize
|
|
)
|
|
|
|
// AccountSeek allows to traverse sub-tree
|
|
type AccountSeek struct {
|
|
seek []byte
|
|
}
|
|
|
|
// StorageSeek allows to traverse sub-tree
|
|
type StorageSeek struct {
|
|
addrHash libcommon.Hash
|
|
incarnation uint64
|
|
seek []byte
|
|
}
|
|
|
|
// AccountItem is an element in the `readWrites` B-tree representing an Ethereum account. It can mean either value
|
|
// just read seek the database and cache (read), or value that is different seek what the last committed value
|
|
// in the DB is (write). Reads can be removed or evicted seek the B-tree at any time, because this
|
|
// does not hurt the consistency. Writes cannot be removed or evicted one by one, therefore they can
|
|
// either be deleted all together, or committed all together and turned into reads.
|
|
type AccountItem struct {
|
|
sequence int
|
|
queuePos int
|
|
flags uint16
|
|
addrHash libcommon.Hash
|
|
account accounts.Account
|
|
}
|
|
|
|
// AccountWriteItem is an item in the `writes` B-tree. As can be seen, it always references a corresponding
|
|
// `AccountItem`. There can be `AccountItem` without corresponding `AccountWriteItem` (in that case `AccountItem`
|
|
// represents a cached read), but there cannot be `AccountWriteItem` without a corresponding `AccountItem`.
|
|
// Such pair represents an account that has been modified in the cache, but the modification has not been committed
|
|
// to the database yet. The correspondence of an `ai AccountItem` and an `awi AccountWriteItem` implies that
|
|
// `keccak(awi.address) == ai.addrHash`.
|
|
type AccountWriteItem struct {
|
|
address libcommon.Address
|
|
ai *AccountItem
|
|
}
|
|
|
|
type StorageItem struct {
|
|
sequence int
|
|
queuePos int
|
|
flags uint16
|
|
addrHash libcommon.Hash
|
|
incarnation uint64
|
|
locHash libcommon.Hash
|
|
value uint256.Int
|
|
}
|
|
|
|
type StorageWriteItem struct {
|
|
address libcommon.Address
|
|
location libcommon.Hash
|
|
si *StorageItem
|
|
}
|
|
|
|
type CodeItem struct {
|
|
sequence int
|
|
queuePos int
|
|
flags uint16
|
|
addrHash libcommon.Hash
|
|
incarnation uint64
|
|
code []byte
|
|
}
|
|
|
|
type CodeWriteItem struct {
|
|
address libcommon.Address
|
|
ci *CodeItem
|
|
}
|
|
|
|
type CacheItem interface {
|
|
btree.Item
|
|
GetSequence() int
|
|
SetSequence(sequence int)
|
|
GetSize() int
|
|
GetQueuePos() int
|
|
SetQueuePos(pos int)
|
|
HasFlag(flag uint16) bool // Check if specified flag is set
|
|
SetFlags(flags uint16) // Set specified flags, but leaves other flags alone
|
|
ClearFlags(flags uint16) // Clear specified flags, but laves other flags alone
|
|
CopyValueFrom(item CacheItem) // Copy value (not key) seek given item
|
|
HasPrefix(prefix CacheItem) bool // Whether this item has specified item as a prefix
|
|
}
|
|
|
|
type CacheWriteItem interface {
|
|
btree.Item
|
|
GetCacheItem() CacheItem
|
|
SetCacheItem(item CacheItem)
|
|
GetSize() int
|
|
}
|
|
|
|
func compare_code_code(i1 *CodeItem, i2 *CodeItem) int {
|
|
c := bytes.Compare(i1.addrHash.Bytes(), i2.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c
|
|
}
|
|
if i1.incarnation == i2.incarnation {
|
|
return 0
|
|
}
|
|
if i1.incarnation < i2.incarnation {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func (r *AccountSeek) Less(than btree.Item) bool {
|
|
switch i := than.(type) {
|
|
case *AccountItem:
|
|
return bytes.Compare(r.seek, i.addrHash.Bytes()) < 0
|
|
case *AccountWriteItem:
|
|
return bytes.Compare(r.seek, i.ai.addrHash.Bytes()) < 0
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", than))
|
|
}
|
|
}
|
|
|
|
func (r *StorageSeek) Less(than btree.Item) bool {
|
|
switch i := than.(type) {
|
|
case *StorageItem:
|
|
c := bytes.Compare(r.addrHash.Bytes(), i.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c < 0
|
|
}
|
|
if r.incarnation < i.incarnation {
|
|
return true
|
|
}
|
|
return bytes.Compare(r.seek, i.locHash.Bytes()) < 0
|
|
case *StorageWriteItem:
|
|
c := bytes.Compare(r.addrHash.Bytes(), i.si.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c < 0
|
|
}
|
|
if r.incarnation < i.si.incarnation {
|
|
return true
|
|
}
|
|
return bytes.Compare(r.seek, i.si.locHash.Bytes()) < 0
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", than))
|
|
}
|
|
}
|
|
|
|
func (ai *AccountItem) Less(than btree.Item) bool {
|
|
switch i := than.(type) {
|
|
case *AccountItem:
|
|
return bytes.Compare(ai.addrHash.Bytes(), i.addrHash.Bytes()) < 0
|
|
case *AccountWriteItem:
|
|
return bytes.Compare(ai.addrHash.Bytes(), i.ai.addrHash.Bytes()) < 0
|
|
case *AccountSeek:
|
|
return bytes.Compare(ai.addrHash.Bytes(), i.seek) < 0
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", than))
|
|
}
|
|
}
|
|
|
|
func (awi *AccountWriteItem) GetCacheItem() CacheItem { return awi.ai }
|
|
func (awi *AccountWriteItem) SetCacheItem(item CacheItem) { awi.ai = item.(*AccountItem) }
|
|
func (awi *AccountWriteItem) GetSize() int { return accountWriteItemSize }
|
|
func (awi *AccountWriteItem) Less(than btree.Item) bool {
|
|
return awi.ai.Less(than)
|
|
}
|
|
|
|
func (ai *AccountItem) GetSequence() int { return ai.sequence }
|
|
func (ai *AccountItem) SetSequence(sequence int) { ai.sequence = sequence }
|
|
func (ai *AccountItem) GetSize() int { return accountItemSize }
|
|
func (ai *AccountItem) GetQueuePos() int { return ai.queuePos }
|
|
func (ai *AccountItem) SetQueuePos(pos int) { ai.queuePos = pos }
|
|
func (ai *AccountItem) HasFlag(flag uint16) bool { return ai.flags&flag != 0 }
|
|
func (ai *AccountItem) SetFlags(flags uint16) { ai.flags |= flags }
|
|
func (ai *AccountItem) ClearFlags(flags uint16) { ai.flags &^= flags }
|
|
func (ai *AccountItem) String() string { return fmt.Sprintf("AccountItem(addrHash=%x)", ai.addrHash) }
|
|
|
|
func (ai *AccountItem) CopyValueFrom(item CacheItem) {
|
|
otherAi, ok := item.(*AccountItem)
|
|
if !ok {
|
|
panic(fmt.Sprintf("expected AccountItem, got %T", item))
|
|
}
|
|
ai.account.Copy(&otherAi.account)
|
|
}
|
|
|
|
func (swi *StorageWriteItem) Less(than btree.Item) bool {
|
|
return swi.si.Less(than.(*StorageWriteItem).si)
|
|
}
|
|
func (swi *StorageWriteItem) GetCacheItem() CacheItem { return swi.si }
|
|
func (swi *StorageWriteItem) SetCacheItem(item CacheItem) { swi.si = item.(*StorageItem) }
|
|
func (swi *StorageWriteItem) GetSize() int { return storageWriteItemSize }
|
|
|
|
func (si *StorageItem) Less(than btree.Item) bool {
|
|
switch i := than.(type) {
|
|
case *StorageItem:
|
|
c := bytes.Compare(si.addrHash.Bytes(), i.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c < 0
|
|
}
|
|
if si.incarnation < i.incarnation {
|
|
return true
|
|
}
|
|
return bytes.Compare(si.locHash.Bytes(), i.locHash.Bytes()) < 0
|
|
case *StorageWriteItem:
|
|
c := bytes.Compare(si.addrHash.Bytes(), i.si.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c < 0
|
|
}
|
|
if si.incarnation < i.si.incarnation {
|
|
return true
|
|
}
|
|
return bytes.Compare(si.locHash.Bytes(), i.si.locHash.Bytes()) < 0
|
|
case *StorageSeek:
|
|
c := bytes.Compare(si.addrHash.Bytes(), i.addrHash.Bytes())
|
|
if c != 0 {
|
|
return c < 0
|
|
}
|
|
if si.incarnation < i.incarnation {
|
|
return true
|
|
}
|
|
return bytes.Compare(si.locHash.Bytes(), i.seek) < 0
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", than))
|
|
}
|
|
}
|
|
func (si *StorageItem) GetSequence() int { return si.sequence }
|
|
func (si *StorageItem) SetSequence(sequence int) { si.sequence = sequence }
|
|
func (si *StorageItem) GetSize() int { return storageItemSize }
|
|
func (si *StorageItem) GetQueuePos() int { return si.queuePos }
|
|
func (si *StorageItem) SetQueuePos(pos int) { si.queuePos = pos }
|
|
func (si *StorageItem) HasFlag(flag uint16) bool { return si.flags&flag != 0 }
|
|
func (si *StorageItem) SetFlags(flags uint16) { si.flags |= flags }
|
|
func (si *StorageItem) ClearFlags(flags uint16) { si.flags &^= flags }
|
|
func (si *StorageItem) String() string {
|
|
return fmt.Sprintf("StorageItem(addrHash=%x,incarnation=%d,locHash=%x)", si.addrHash, si.incarnation, si.locHash)
|
|
}
|
|
|
|
func (si *StorageItem) CopyValueFrom(item CacheItem) {
|
|
otherSi, ok := item.(*StorageItem)
|
|
if !ok {
|
|
panic(fmt.Sprintf("expected StorageCacheItem, got %T", item))
|
|
}
|
|
si.value.Set(&otherSi.value)
|
|
}
|
|
|
|
func (ci *CodeItem) Less(than btree.Item) bool {
|
|
return compare_code_code(ci, than.(*CodeItem)) < 0
|
|
}
|
|
|
|
func (cwi *CodeWriteItem) Less(than btree.Item) bool {
|
|
i := than.(*CodeWriteItem)
|
|
c := bytes.Compare(cwi.address.Bytes(), i.address.Bytes())
|
|
if c == 0 {
|
|
return cwi.ci.incarnation < i.ci.incarnation
|
|
}
|
|
return c < 0
|
|
}
|
|
|
|
func (cwi *CodeWriteItem) GetCacheItem() CacheItem { return cwi.ci }
|
|
func (cwi *CodeWriteItem) SetCacheItem(item CacheItem) { cwi.ci = item.(*CodeItem) }
|
|
func (cwi *CodeWriteItem) GetSize() int { return codeWriteItemSize + len(cwi.ci.code) }
|
|
func (ci *CodeItem) GetSequence() int { return ci.sequence }
|
|
func (ci *CodeItem) SetSequence(sequence int) { ci.sequence = sequence }
|
|
func (ci *CodeItem) GetSize() int { return codeItemSize + len(ci.code) }
|
|
func (ci *CodeItem) GetQueuePos() int { return ci.queuePos }
|
|
func (ci *CodeItem) SetQueuePos(pos int) { ci.queuePos = pos }
|
|
func (ci *CodeItem) HasFlag(flag uint16) bool { return ci.flags&flag != 0 }
|
|
func (ci *CodeItem) SetFlags(flags uint16) { ci.flags |= flags }
|
|
func (ci *CodeItem) ClearFlags(flags uint16) { ci.flags &^= flags }
|
|
func (ci *CodeItem) String() string {
|
|
return fmt.Sprintf("CodeItem(addrHash=%x,incarnation=%d)", ci.addrHash, ci.incarnation)
|
|
}
|
|
|
|
func (ci *CodeItem) CopyValueFrom(item CacheItem) {
|
|
otherCi, ok := item.(*CodeItem)
|
|
if !ok {
|
|
panic(fmt.Sprintf("expected CodeCacheItem, got %T", item))
|
|
}
|
|
ci.code = make([]byte, len(otherCi.code))
|
|
copy(ci.code, otherCi.code)
|
|
}
|
|
|
|
// Heap for reads
|
|
type ReadHeap struct {
|
|
items []CacheItem
|
|
}
|
|
|
|
func (rh ReadHeap) Len() int { return len(rh.items) }
|
|
func (rh ReadHeap) Less(i, j int) bool { return rh.items[i].GetSequence() < rh.items[j].GetSequence() }
|
|
func (rh ReadHeap) Swap(i, j int) {
|
|
// Swap queue positions in the B-tree leaves too
|
|
rh.items[i].SetQueuePos(j)
|
|
rh.items[j].SetQueuePos(i)
|
|
rh.items[i], rh.items[j] = rh.items[j], rh.items[i]
|
|
}
|
|
|
|
func (rh *ReadHeap) Push(x interface{}) {
|
|
// Push and Pop use pointer receivers because they modify the slice's length,
|
|
// not just its contents.
|
|
cacheItem := x.(CacheItem)
|
|
cacheItem.SetQueuePos(len(rh.items))
|
|
rh.items = append(rh.items, cacheItem)
|
|
}
|
|
|
|
func (rh *ReadHeap) Pop() interface{} {
|
|
cacheItem := rh.items[len(rh.items)-1]
|
|
rh.items = rh.items[:len(rh.items)-1]
|
|
return cacheItem
|
|
}
|
|
|
|
// StateCache is the structure containing B-trees and priority queues for the state cache
|
|
type StateCache struct {
|
|
readWrites [5]*btree.BTree // Mixed reads and writes
|
|
writes [5]*btree.BTree // Only writes for the effective iteration
|
|
readQueue [5]ReadHeap // Priority queue of read elements eligible for eviction (sorted by sequence)
|
|
limit datasize.ByteSize // Total size of the readQueue (if new item causes the size to go over the limit, some existing items are evicted)
|
|
readSize int
|
|
writeSize int
|
|
sequence int // Current sequence assigned to any item that has been "touched" (created, deleted, read). Incremented after every touch
|
|
unprocQueue [5]UnprocessedHeap // Priority queue of items appeared since last root calculation processing (sorted by the keys - addrHash, incarnation, locHash)
|
|
}
|
|
|
|
func id(a interface{}) uint8 {
|
|
switch a.(type) {
|
|
case *AccountItem, *AccountWriteItem, *AccountSeek:
|
|
return 0
|
|
case *StorageItem, *StorageWriteItem, *StorageSeek:
|
|
return 1
|
|
case *CodeItem, *CodeWriteItem:
|
|
return 2
|
|
case *AccountHashItem, *AccountHashWriteItem:
|
|
return 3
|
|
case *StorageHashItem, *StorageHashWriteItem:
|
|
return 4
|
|
default:
|
|
panic(fmt.Sprintf("unexpected type: %T", a))
|
|
}
|
|
}
|
|
|
|
// NewStateCache create a new state cache based on the B-trees of specific degree. The second and the third parameters are the limit on the number of reads and writes to cache, respectively
|
|
func NewStateCache(degree int, limit datasize.ByteSize) *StateCache {
|
|
var sc StateCache
|
|
sc.limit = limit
|
|
for i := 0; i < len(sc.readWrites); i++ {
|
|
sc.readWrites[i] = btree.New(degree)
|
|
}
|
|
for i := 0; i < len(sc.writes); i++ {
|
|
sc.writes[i] = btree.New(degree)
|
|
}
|
|
for i := 0; i < len(sc.readQueue); i++ {
|
|
heap.Init(&sc.readQueue[i])
|
|
}
|
|
for i := 0; i < len(sc.unprocQueue); i++ {
|
|
heap.Init(&sc.unprocQueue[i])
|
|
}
|
|
return &sc
|
|
}
|
|
|
|
// Clone creates a clone cache which can be modified independently, but it shares the parts of the cache that are common
|
|
func (sc *StateCache) Clone() *StateCache {
|
|
var clone StateCache
|
|
for i := range clone.readWrites {
|
|
clone.readWrites[i] = sc.readWrites[i].Clone()
|
|
clone.writes[i] = sc.writes[i].Clone()
|
|
clone.limit = sc.limit
|
|
heap.Init(&clone.readQueue[i])
|
|
heap.Init(&clone.unprocQueue[i])
|
|
}
|
|
return &clone
|
|
}
|
|
|
|
func (sc *StateCache) get(key btree.Item) (CacheItem, bool) {
|
|
WritesRead.Inc()
|
|
item := sc.readWrites[id(key)].Get(key)
|
|
if item == nil {
|
|
return nil, false
|
|
}
|
|
cacheItem := item.(CacheItem)
|
|
if cacheItem.HasFlag(DeletedFlag) || cacheItem.HasFlag(AbsentFlag) {
|
|
return nil, true
|
|
}
|
|
return cacheItem, true
|
|
}
|
|
|
|
// GetAccount searches and account with given address, without modifying any structures
|
|
// Second return value is true if such account is found
|
|
func (sc *StateCache) GetAccount(address []byte) (*accounts.Account, bool) {
|
|
AccRead.Inc()
|
|
var key AccountItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(key.addrHash[:])
|
|
if item, ok := sc.get(&key); ok {
|
|
if item != nil {
|
|
return &item.(*AccountItem).account, true
|
|
}
|
|
return nil, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (sc *StateCache) HasAccountWithInPrefix(addrHashPrefix []byte) bool {
|
|
AccRead.Inc()
|
|
seek := &AccountSeek{seek: addrHashPrefix}
|
|
var found bool
|
|
sc.readWrites[id(seek)].AscendGreaterOrEqual(seek, func(i btree.Item) bool {
|
|
found = bytes.HasPrefix(i.(*AccountItem).addrHash.Bytes(), addrHashPrefix)
|
|
return false
|
|
})
|
|
return found
|
|
}
|
|
|
|
// GetDeletedAccount attempts to retrieve the last version of account before it was deleted
|
|
func (sc *StateCache) GetDeletedAccount(address []byte) *accounts.Account {
|
|
key := &AccountItem{}
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(key.addrHash[:])
|
|
item := sc.readWrites[id(key)].Get(key)
|
|
if item == nil {
|
|
return nil
|
|
}
|
|
ai := item.(*AccountItem)
|
|
if !ai.HasFlag(DeletedFlag) {
|
|
return nil
|
|
}
|
|
return &ai.account
|
|
}
|
|
|
|
// GetStorage searches storage item with given address, incarnation, and location, without modifying any structures
|
|
// Second return value is true if such item is found
|
|
func (sc *StateCache) GetStorage(address []byte, incarnation uint64, location []byte) ([]byte, bool) {
|
|
StRead.Inc()
|
|
var key StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(key.addrHash[:])
|
|
key.incarnation = incarnation
|
|
h.Sha.Reset()
|
|
//nolint:errcheck
|
|
h.Sha.Write(location)
|
|
//nolint:errcheck
|
|
h.Sha.Read(key.locHash[:])
|
|
if item, ok := sc.get(&key); ok {
|
|
if item != nil {
|
|
return item.(*StorageItem).value.Bytes(), true
|
|
}
|
|
return nil, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// GetCode searches contract code with given address, without modifying any structures
|
|
// Second return value is true if such item is found
|
|
func (sc *StateCache) GetCode(address []byte, incarnation uint64) ([]byte, bool) {
|
|
var key CodeItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(key.addrHash[:])
|
|
key.incarnation = incarnation
|
|
if item, ok := sc.get(&key); ok {
|
|
if item != nil {
|
|
return item.(*CodeItem).code, true
|
|
}
|
|
return nil, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (sc *StateCache) setRead(item CacheItem, absent bool) {
|
|
id := id(item)
|
|
if sc.readWrites[id].Get(item) != nil {
|
|
panic(fmt.Sprintf("item must not be present in the cache before doing setRead: %s", item))
|
|
}
|
|
item.SetSequence(sc.sequence)
|
|
sc.sequence++
|
|
item.ClearFlags(ModifiedFlag | DeletedFlag)
|
|
if absent {
|
|
item.SetFlags(AbsentFlag)
|
|
} else {
|
|
item.ClearFlags(AbsentFlag)
|
|
}
|
|
|
|
if sc.limit != 0 && sc.readSize+item.GetSize() > int(sc.limit) {
|
|
for sc.readQueue[id].Len() > 0 && sc.readSize+item.GetSize() > int(sc.limit) {
|
|
// Read queue cannot grow anymore, need to evict one element
|
|
cacheItem := heap.Pop(&sc.readQueue[id]).(CacheItem)
|
|
sc.readSize -= cacheItem.GetSize()
|
|
sc.readWrites[id].Delete(cacheItem)
|
|
}
|
|
}
|
|
// Push new element on the read queue
|
|
heap.Push(&sc.readQueue[id], item)
|
|
sc.readWrites[id].ReplaceOrInsert(item)
|
|
sc.readSize += item.GetSize()
|
|
}
|
|
|
|
func (sc *StateCache) readQueuesLen() (res int) {
|
|
for i := 0; i < len(sc.readQueue); i++ {
|
|
res += sc.readQueue[i].Len()
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetAccountRead adds given account to the cache, marking it as a read (not written)
|
|
func (sc *StateCache) SetAccountRead(address []byte, account *accounts.Account) {
|
|
var ai AccountItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ai.addrHash[:])
|
|
ai.account.Copy(account)
|
|
sc.setRead(&ai, false /* absent */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetAccountRead(addrHash libcommon.Hash, account *accounts.Account) {
|
|
var ai AccountItem
|
|
ai.addrHash.SetBytes(addrHash.Bytes())
|
|
ai.account.Copy(account)
|
|
sc.setRead(&ai, false /* absent */)
|
|
}
|
|
|
|
func (sc *StateCache) GetAccountByHashedAddress(addrHash libcommon.Hash) (*accounts.Account, bool) {
|
|
var key AccountItem
|
|
key.addrHash.SetBytes(addrHash.Bytes())
|
|
if item, ok := sc.get(&key); ok {
|
|
if item != nil {
|
|
return &item.(*AccountItem).account, true
|
|
}
|
|
return nil, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (sc *StateCache) GetStorageByHashedAddress(addrHash libcommon.Hash, incarnation uint64, locHash libcommon.Hash) ([]byte, bool) {
|
|
key := StorageItem{
|
|
addrHash: addrHash,
|
|
incarnation: incarnation,
|
|
locHash: locHash,
|
|
}
|
|
if item, ok := sc.get(&key); ok {
|
|
if item != nil {
|
|
return item.(*StorageItem).value.Bytes(), true
|
|
}
|
|
return nil, true
|
|
}
|
|
return nil, false
|
|
|
|
}
|
|
|
|
// SetAccountRead adds given account address to the cache, marking it as a absent
|
|
func (sc *StateCache) SetAccountAbsent(address []byte) {
|
|
var ai AccountItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ai.addrHash[:])
|
|
sc.setRead(&ai, true /* absent */)
|
|
}
|
|
|
|
func (sc *StateCache) setWrite(item CacheItem, writeItem CacheWriteItem, del bool) {
|
|
id := id(item)
|
|
// Check if this is going to be modification of the existing entry
|
|
if existing := sc.writes[id].Get(writeItem); existing != nil {
|
|
cacheWriteItem := existing.(CacheWriteItem)
|
|
cacheItem := cacheWriteItem.GetCacheItem()
|
|
sc.readSize += item.GetSize()
|
|
sc.readSize -= cacheItem.GetSize()
|
|
sc.writeSize += writeItem.GetSize()
|
|
sc.writeSize -= cacheWriteItem.GetSize()
|
|
if del {
|
|
cacheItem.SetFlags(DeletedFlag)
|
|
} else {
|
|
cacheItem.CopyValueFrom(item)
|
|
cacheItem.ClearFlags(DeletedFlag)
|
|
}
|
|
cacheItem.SetSequence(sc.sequence)
|
|
sc.sequence++
|
|
return
|
|
}
|
|
// Now see if there is such item in the readWrite B-tree - then we replace read entry with write entry
|
|
if existing := sc.readWrites[id].Get(item); existing != nil {
|
|
cacheItem := existing.(CacheItem)
|
|
// Remove seek the reads queue
|
|
if sc.readQueue[id].Len() > 0 {
|
|
heap.Remove(&sc.readQueue[id], cacheItem.GetQueuePos())
|
|
}
|
|
sc.readSize += item.GetSize()
|
|
sc.readSize -= cacheItem.GetSize()
|
|
cacheItem.SetFlags(ModifiedFlag)
|
|
cacheItem.ClearFlags(AbsentFlag)
|
|
if del {
|
|
cacheItem.SetFlags(DeletedFlag)
|
|
} else {
|
|
cacheItem.CopyValueFrom(item)
|
|
cacheItem.ClearFlags(DeletedFlag)
|
|
}
|
|
cacheItem.SetSequence(sc.sequence)
|
|
sc.sequence++
|
|
writeItem.SetCacheItem(cacheItem)
|
|
sc.writes[id].ReplaceOrInsert(writeItem)
|
|
sc.writeSize += writeItem.GetSize()
|
|
return
|
|
}
|
|
if sc.limit != 0 && sc.readSize+item.GetSize() > int(sc.limit) {
|
|
for sc.readQueue[id].Len() > 0 && sc.readSize+item.GetSize() > int(sc.limit) {
|
|
// There is no space available, need to evict one read element
|
|
cacheItem := heap.Pop(&sc.readQueue[id]).(CacheItem)
|
|
sc.readWrites[id].Delete(cacheItem)
|
|
sc.readSize -= cacheItem.GetSize()
|
|
}
|
|
}
|
|
item.SetSequence(sc.sequence)
|
|
sc.sequence++
|
|
item.SetFlags(ModifiedFlag)
|
|
item.ClearFlags(AbsentFlag)
|
|
if del {
|
|
item.SetFlags(DeletedFlag)
|
|
} else {
|
|
item.ClearFlags(DeletedFlag)
|
|
}
|
|
sc.readWrites[id].ReplaceOrInsert(item)
|
|
sc.readSize += item.GetSize()
|
|
writeItem.SetCacheItem(item)
|
|
sc.writes[id].ReplaceOrInsert(writeItem)
|
|
sc.writeSize += writeItem.GetSize()
|
|
}
|
|
|
|
// SetAccountWrite adds given account to the cache, marking it as written (cannot be evicted)
|
|
func (sc *StateCache) SetAccountWrite(address []byte, account *accounts.Account) {
|
|
var ai AccountItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ai.addrHash[:])
|
|
ai.account.Copy(account)
|
|
var awi AccountWriteItem
|
|
copy(awi.address[:], address)
|
|
awi.ai = &ai
|
|
sc.setWrite(&ai, &awi, false /* delete */)
|
|
}
|
|
|
|
// SetAccountDelete is very similar to SetAccountWrite with the difference that there no set value
|
|
func (sc *StateCache) SetAccountDelete(address []byte) {
|
|
var ai AccountItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ai.addrHash[:])
|
|
var awi AccountWriteItem
|
|
copy(awi.address[:], address)
|
|
awi.ai = &ai
|
|
sc.setWrite(&ai, &awi, true /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) SetStorageRead(address []byte, incarnation uint64, location []byte, value []byte) {
|
|
var si StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.addrHash[:])
|
|
si.incarnation = incarnation
|
|
h.Sha.Reset()
|
|
//nolint:errcheck
|
|
h.Sha.Write(location)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.locHash[:])
|
|
si.value.SetBytes(value)
|
|
sc.setRead(&si, false /* absent */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetStorageRead(addrHash libcommon.Hash, incarnation uint64, locHash libcommon.Hash, val []byte) {
|
|
var i StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
copy(i.addrHash[:], addrHash.Bytes())
|
|
i.incarnation = incarnation
|
|
i.locHash.SetBytes(locHash.Bytes())
|
|
i.value.SetBytes(val)
|
|
sc.setRead(&i, false /* absent */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetAccountWrite(addrHash libcommon.Hash, account *accounts.Account) {
|
|
var ai AccountItem
|
|
copy(ai.addrHash[:], addrHash.Bytes())
|
|
ai.account.Copy(account)
|
|
var awi AccountWriteItem
|
|
awi.ai = &ai
|
|
sc.setWrite(&ai, &awi, false /* delete */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetAccountDelete(addrHash libcommon.Hash) {
|
|
var ai AccountItem
|
|
copy(ai.addrHash[:], addrHash.Bytes())
|
|
var awi AccountWriteItem
|
|
awi.ai = &ai
|
|
sc.setWrite(&ai, &awi, true /* delete */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetStorageDelete(addrHash libcommon.Hash, incarnation uint64, locHash libcommon.Hash) {
|
|
var si StorageItem
|
|
copy(si.addrHash[:], addrHash.Bytes())
|
|
si.incarnation = incarnation
|
|
copy(si.locHash[:], locHash.Bytes())
|
|
var swi StorageWriteItem
|
|
swi.si = &si
|
|
sc.setWrite(&si, &swi, true /* delete */)
|
|
}
|
|
|
|
// hack to set hashed addr - we don't have another one in trie stage
|
|
func (sc *StateCache) DeprecatedSetStorageWrite(addrHash libcommon.Hash, incarnation uint64, locHash libcommon.Hash, v []byte) {
|
|
var si StorageItem
|
|
copy(si.addrHash[:], addrHash.Bytes())
|
|
si.incarnation = incarnation
|
|
copy(si.locHash[:], locHash.Bytes())
|
|
si.value.SetBytes(v)
|
|
var swi StorageWriteItem
|
|
swi.si = &si
|
|
sc.setWrite(&si, &swi, false /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) SetStorageAbsent(address []byte, incarnation uint64, location []byte) {
|
|
var si StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.addrHash[:])
|
|
si.incarnation = incarnation
|
|
h.Sha.Reset()
|
|
//nolint:errcheck
|
|
h.Sha.Write(location)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.locHash[:])
|
|
sc.setRead(&si, true /* absent */)
|
|
}
|
|
|
|
func (sc *StateCache) SetStorageWrite(address []byte, incarnation uint64, location []byte, value []byte) {
|
|
var si StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.addrHash[:])
|
|
si.incarnation = incarnation
|
|
h.Sha.Reset()
|
|
//nolint:errcheck
|
|
h.Sha.Write(location)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.locHash[:])
|
|
si.value.SetBytes(value)
|
|
var swi StorageWriteItem
|
|
copy(swi.address[:], address)
|
|
copy(swi.location[:], location)
|
|
swi.si = &si
|
|
sc.setWrite(&si, &swi, false /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) SetStorageDelete(address []byte, incarnation uint64, location []byte) {
|
|
var si StorageItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.addrHash[:])
|
|
si.incarnation = incarnation
|
|
h.Sha.Reset()
|
|
//nolint:errcheck
|
|
h.Sha.Write(location)
|
|
//nolint:errcheck
|
|
h.Sha.Read(si.locHash[:])
|
|
var swi StorageWriteItem
|
|
copy(swi.address[:], address)
|
|
copy(swi.location[:], location)
|
|
swi.si = &si
|
|
sc.setWrite(&si, &swi, true /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) SetCodeRead(address []byte, incarnation uint64, code []byte) {
|
|
var ci CodeItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ci.addrHash[:])
|
|
ci.incarnation = incarnation
|
|
ci.code = make([]byte, len(code))
|
|
copy(ci.code, code)
|
|
sc.setRead(&ci, false /* absent */)
|
|
}
|
|
|
|
func (sc *StateCache) SetCodeAbsent(address []byte, incarnation uint64) {
|
|
var ci CodeItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ci.addrHash[:])
|
|
ci.incarnation = incarnation
|
|
sc.setRead(&ci, true /* absent */)
|
|
}
|
|
|
|
func (sc *StateCache) SetCodeWrite(address []byte, incarnation uint64, code []byte) {
|
|
// Check if this is going to be modification of the existing entry
|
|
var ci CodeItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ci.addrHash[:])
|
|
ci.incarnation = incarnation
|
|
ci.code = make([]byte, len(code))
|
|
copy(ci.code, code)
|
|
var cwi CodeWriteItem
|
|
copy(cwi.address[:], address)
|
|
cwi.ci = &ci
|
|
sc.setWrite(&ci, &cwi, false /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) SetCodeDelete(address []byte, incarnation uint64) {
|
|
// Check if this is going to be modification of the existing entry
|
|
var ci CodeItem
|
|
h := common.NewHasher()
|
|
defer common.ReturnHasherToPool(h)
|
|
//nolint:errcheck
|
|
h.Sha.Write(address)
|
|
//nolint:errcheck
|
|
h.Sha.Read(ci.addrHash[:])
|
|
ci.incarnation = incarnation
|
|
ci.code = nil
|
|
var cwi CodeWriteItem
|
|
copy(cwi.address[:], address)
|
|
cwi.ci = &ci
|
|
sc.setWrite(&ci, &cwi, true /* delete */)
|
|
}
|
|
|
|
func (sc *StateCache) PrepareWrites() [5]*btree.BTree {
|
|
var writes [5]*btree.BTree
|
|
for i := 0; i < len(sc.writes); i++ {
|
|
sc.writes[i].Ascend(func(i btree.Item) bool {
|
|
writeItem := i.(CacheWriteItem)
|
|
cacheItem := writeItem.GetCacheItem()
|
|
cacheItem.ClearFlags(ModifiedFlag)
|
|
if cacheItem.HasFlag(DeletedFlag) {
|
|
cacheItem.ClearFlags(DeletedFlag)
|
|
cacheItem.SetFlags(AbsentFlag)
|
|
}
|
|
return true
|
|
})
|
|
writes[i] = sc.writes[i].Clone()
|
|
sc.writes[i].Clear(true /* addNodesToFreeList */)
|
|
sc.writeSize = 0
|
|
}
|
|
return writes
|
|
}
|
|
|
|
func WalkWrites(
|
|
writes [5]*btree.BTree,
|
|
accountWrite func(address []byte, account *accounts.Account) error,
|
|
accountDelete func(address []byte, original *accounts.Account) error,
|
|
storageWrite func(address []byte, incarnation uint64, location []byte, value []byte) error,
|
|
storageDelete func(address []byte, incarnation uint64, location []byte) error,
|
|
codeWrite func(address []byte, incarnation uint64, code []byte) error,
|
|
codeDelete func(address []byte, incarnation uint64) error,
|
|
) error {
|
|
var err error
|
|
for i := 0; i < len(writes); i++ {
|
|
writes[i].Ascend(func(i btree.Item) bool {
|
|
switch it := i.(type) {
|
|
case *AccountWriteItem:
|
|
if it.ai.flags&AbsentFlag != 0 {
|
|
if err = accountDelete(it.address.Bytes(), &it.ai.account); err != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
if err = accountWrite(it.address.Bytes(), &it.ai.account); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
case *StorageWriteItem:
|
|
if it.si.flags&AbsentFlag != 0 {
|
|
if err = storageDelete(it.address.Bytes(), it.si.incarnation, it.location.Bytes()); err != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
if err = storageWrite(it.address.Bytes(), it.si.incarnation, it.location.Bytes(), it.si.value.Bytes()); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
case *CodeWriteItem:
|
|
if it.ci.flags&AbsentFlag != 0 {
|
|
if err = codeDelete(it.address.Bytes(), it.ci.incarnation); err != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
if err = codeWrite(it.address.Bytes(), it.ci.incarnation, it.ci.code); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (sc *StateCache) TurnWritesToReads(writes [5]*btree.BTree) {
|
|
for i := 0; i < len(writes); i++ {
|
|
readQueue := &sc.readQueue[i]
|
|
writes[i].Ascend(func(it btree.Item) bool {
|
|
cacheWriteItem := it.(CacheWriteItem)
|
|
cacheItem := cacheWriteItem.GetCacheItem()
|
|
if !cacheItem.HasFlag(ModifiedFlag) {
|
|
// Cannot touch items that have been modified since we have taken away the writes
|
|
heap.Push(readQueue, cacheItem)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
|
|
func (sc *StateCache) TotalCount() (res int) {
|
|
for i := 0; i < len(sc.readWrites); i++ {
|
|
res += sc.readWrites[i].Len()
|
|
}
|
|
return
|
|
}
|
|
func (sc *StateCache) WriteCount() (res int) {
|
|
for i := 0; i < len(sc.readWrites); i++ {
|
|
res += sc.writes[i].Len()
|
|
}
|
|
return
|
|
}
|
|
func (sc *StateCache) WriteSize() int { return sc.writeSize }
|
|
func (sc *StateCache) ReadSize() int { return sc.readSize }
|