mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-26 05:27:19 +00:00
3db7e85994
* no reason to copy if we work with ethdb.Tx (user of ethdb.Tx expecting that all data valid until end of tx) * less objects
354 lines
9.2 KiB
Go
354 lines
9.2 KiB
Go
package state
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/RoaringBitmap/roaring/roaring64"
|
|
"github.com/ledgerwatch/erigon/common"
|
|
"github.com/ledgerwatch/erigon/common/changeset"
|
|
"github.com/ledgerwatch/erigon/common/dbutils"
|
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
|
"github.com/ledgerwatch/erigon/ethdb"
|
|
"github.com/ledgerwatch/erigon/ethdb/bitmapdb"
|
|
)
|
|
|
|
func GetAsOf(tx ethdb.Tx, storage bool, key []byte, timestamp uint64) ([]byte, error) {
|
|
v, err := FindByHistory(tx, storage, key, timestamp)
|
|
if err == nil {
|
|
return v, nil
|
|
}
|
|
if !errors.Is(err, ethdb.ErrKeyNotFound) {
|
|
return nil, err
|
|
}
|
|
return tx.GetOne(dbutils.PlainStateBucket, key)
|
|
}
|
|
|
|
func FindByHistory(tx ethdb.Tx, storage bool, key []byte, timestamp uint64) ([]byte, error) {
|
|
var csBucket string
|
|
if storage {
|
|
csBucket = dbutils.StorageChangeSetBucket
|
|
} else {
|
|
csBucket = dbutils.AccountChangeSetBucket
|
|
}
|
|
|
|
ch, err := tx.Cursor(changeset.Mapper[csBucket].IndexBucket)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer ch.Close()
|
|
k, v, seekErr := ch.Seek(changeset.Mapper[csBucket].IndexChunkKey(key, timestamp))
|
|
if seekErr != nil {
|
|
return nil, seekErr
|
|
}
|
|
|
|
if k == nil {
|
|
return nil, ethdb.ErrKeyNotFound
|
|
}
|
|
if storage {
|
|
if !bytes.Equal(k[:common.AddressLength], key[:common.AddressLength]) ||
|
|
!bytes.Equal(k[common.AddressLength:common.AddressLength+common.HashLength], key[common.AddressLength+common.IncarnationLength:]) {
|
|
return nil, ethdb.ErrKeyNotFound
|
|
}
|
|
} else {
|
|
if !bytes.HasPrefix(k, key) {
|
|
return nil, ethdb.ErrKeyNotFound
|
|
}
|
|
}
|
|
index := roaring64.New()
|
|
if _, err := index.ReadFrom(bytes.NewReader(v)); err != nil {
|
|
return nil, err
|
|
}
|
|
found, ok := bitmapdb.SeekInBitmap64(index, timestamp)
|
|
changeSetBlock := found
|
|
|
|
var data []byte
|
|
if ok {
|
|
c, err := tx.CursorDupSort(csBucket)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer c.Close()
|
|
if storage {
|
|
data, err = changeset.Mapper[csBucket].Find(c, changeSetBlock, key)
|
|
} else {
|
|
data, err = changeset.Mapper[csBucket].Find(c, changeSetBlock, key)
|
|
}
|
|
if err != nil {
|
|
if !errors.Is(err, changeset.ErrNotFound) {
|
|
return nil, fmt.Errorf("finding %x in the changeset %d: %w", key, changeSetBlock, err)
|
|
}
|
|
return nil, ethdb.ErrKeyNotFound
|
|
}
|
|
} else {
|
|
return nil, ethdb.ErrKeyNotFound
|
|
}
|
|
|
|
//restore codehash
|
|
if !storage {
|
|
var acc accounts.Account
|
|
if err := acc.DecodeForStorage(data); err != nil {
|
|
return nil, err
|
|
}
|
|
if acc.Incarnation > 0 && acc.IsEmptyCodeHash() {
|
|
var codeHash []byte
|
|
var err error
|
|
codeHash, err = tx.GetOne(dbutils.PlainContractCodeBucket, dbutils.PlainGenerateStoragePrefix(key, acc.Incarnation))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(codeHash) > 0 {
|
|
acc.CodeHash = common.BytesToHash(codeHash)
|
|
}
|
|
data = make([]byte, acc.EncodingLengthForStorage())
|
|
acc.EncodeForStorage(data)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// startKey is the concatenation of address and incarnation (BigEndian 8 byte)
|
|
func WalkAsOfStorage(tx ethdb.Tx, address common.Address, incarnation uint64, startLocation common.Hash, timestamp uint64, walker func(k1, k2, v []byte) (bool, error)) error {
|
|
var startkey = make([]byte, common.AddressLength+common.IncarnationLength+common.HashLength)
|
|
copy(startkey, address.Bytes())
|
|
binary.BigEndian.PutUint64(startkey[common.AddressLength:], incarnation)
|
|
copy(startkey[common.AddressLength+common.IncarnationLength:], startLocation.Bytes())
|
|
|
|
var startkeyNoInc = make([]byte, common.AddressLength+common.HashLength)
|
|
copy(startkeyNoInc, address.Bytes())
|
|
copy(startkeyNoInc[common.AddressLength:], startLocation.Bytes())
|
|
|
|
//for storage
|
|
mCursor, err := tx.Cursor(dbutils.PlainStateBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer mCursor.Close()
|
|
mainCursor := ethdb.NewSplitCursor(
|
|
mCursor,
|
|
startkey,
|
|
8*(common.AddressLength+common.IncarnationLength),
|
|
common.AddressLength, /* part1end */
|
|
common.AddressLength+common.IncarnationLength, /* part2start */
|
|
common.AddressLength+common.IncarnationLength+common.HashLength, /* part3start */
|
|
)
|
|
|
|
//for historic data
|
|
shCursor, err := tx.Cursor(dbutils.StorageHistoryBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer shCursor.Close()
|
|
var hCursor = ethdb.NewSplitCursor(
|
|
shCursor,
|
|
startkeyNoInc,
|
|
8*common.AddressLength,
|
|
common.AddressLength, /* part1end */
|
|
common.AddressLength, /* part2start */
|
|
common.AddressLength+common.HashLength, /* part3start */
|
|
)
|
|
csCursor, err := tx.CursorDupSort(dbutils.StorageChangeSetBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer csCursor.Close()
|
|
|
|
addr, loc, _, v, err1 := mainCursor.Seek()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
|
|
hAddr, hLoc, tsEnc, hV, err2 := hCursor.Seek()
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
for hLoc != nil && binary.BigEndian.Uint64(tsEnc) < timestamp {
|
|
if hAddr, hLoc, tsEnc, hV, err2 = hCursor.Next(); err2 != nil {
|
|
return err2
|
|
}
|
|
}
|
|
goOn := true
|
|
for goOn {
|
|
cmp, br := common.KeyCmp(addr, hAddr)
|
|
if br {
|
|
break
|
|
}
|
|
if cmp == 0 {
|
|
cmp, br = common.KeyCmp(loc, hLoc)
|
|
}
|
|
if br {
|
|
break
|
|
}
|
|
|
|
//next key in state
|
|
if cmp < 0 {
|
|
goOn, err = walker(addr, loc, v)
|
|
} else {
|
|
index := roaring64.New()
|
|
if _, err = index.ReadFrom(bytes.NewReader(hV)); err != nil {
|
|
return err
|
|
}
|
|
found, ok := bitmapdb.SeekInBitmap64(index, timestamp)
|
|
changeSetBlock := found
|
|
|
|
if ok {
|
|
// Extract value from the changeSet
|
|
csKey := make([]byte, 8+common.AddressLength+common.IncarnationLength)
|
|
copy(csKey[:], dbutils.EncodeBlockNumber(changeSetBlock))
|
|
copy(csKey[8:], address[:]) // address + incarnation
|
|
binary.BigEndian.PutUint64(csKey[8+common.AddressLength:], incarnation)
|
|
kData := csKey
|
|
data, err3 := csCursor.SeekBothRange(csKey, hLoc)
|
|
if err3 != nil {
|
|
return err3
|
|
}
|
|
if !bytes.Equal(kData, csKey) || !bytes.HasPrefix(data, hLoc) {
|
|
return fmt.Errorf("inconsistent storage changeset and history kData %x, csKey %x, data %x, hLoc %x", kData, csKey, data, hLoc)
|
|
}
|
|
data = data[common.HashLength:]
|
|
if len(data) > 0 { // Skip deleted entries
|
|
goOn, err = walker(hAddr, hLoc, data)
|
|
}
|
|
} else if cmp == 0 {
|
|
goOn, err = walker(addr, loc, v)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if goOn {
|
|
if cmp <= 0 {
|
|
if addr, loc, _, v, err1 = mainCursor.Next(); err1 != nil {
|
|
return err1
|
|
}
|
|
}
|
|
if cmp >= 0 {
|
|
hLoc0 := hLoc
|
|
for hLoc != nil && (bytes.Equal(hLoc0, hLoc) || binary.BigEndian.Uint64(tsEnc) < timestamp) {
|
|
if hAddr, hLoc, tsEnc, hV, err2 = hCursor.Next(); err2 != nil {
|
|
return err2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func WalkAsOfAccounts(tx ethdb.Tx, startAddress common.Address, timestamp uint64, walker func(k []byte, v []byte) (bool, error)) error {
|
|
mainCursor, err := tx.Cursor(dbutils.PlainStateBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer mainCursor.Close()
|
|
ahCursor, err := tx.Cursor(dbutils.AccountsHistoryBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer ahCursor.Close()
|
|
var hCursor = ethdb.NewSplitCursor(
|
|
ahCursor,
|
|
startAddress.Bytes(),
|
|
0, /* fixedBits */
|
|
common.AddressLength, /* part1end */
|
|
common.AddressLength, /* part2start */
|
|
common.AddressLength+8, /* part3start */
|
|
)
|
|
csCursor, err := tx.CursorDupSort(dbutils.AccountChangeSetBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer csCursor.Close()
|
|
|
|
k, v, err1 := mainCursor.Seek(startAddress.Bytes())
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
for k != nil && len(k) > common.AddressLength {
|
|
k, v, err1 = mainCursor.Next()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
}
|
|
hK, tsEnc, _, hV, err2 := hCursor.Seek()
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
for hK != nil && binary.BigEndian.Uint64(tsEnc) < timestamp {
|
|
hK, tsEnc, _, hV, err2 = hCursor.Next()
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
}
|
|
|
|
goOn := true
|
|
for goOn {
|
|
//exit or next conditions
|
|
cmp, br := common.KeyCmp(k, hK)
|
|
if br {
|
|
break
|
|
}
|
|
if cmp < 0 {
|
|
goOn, err = walker(k, v)
|
|
} else {
|
|
index := roaring64.New()
|
|
_, err = index.ReadFrom(bytes.NewReader(hV))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
found, ok := bitmapdb.SeekInBitmap64(index, timestamp)
|
|
changeSetBlock := found
|
|
if ok {
|
|
// Extract value from the changeSet
|
|
csKey := dbutils.EncodeBlockNumber(changeSetBlock)
|
|
kData := csKey
|
|
data, err3 := csCursor.SeekBothRange(csKey, hK)
|
|
if err3 != nil {
|
|
return err3
|
|
}
|
|
if !bytes.Equal(kData, csKey) || !bytes.HasPrefix(data, hK) {
|
|
return fmt.Errorf("inconsistent account history and changesets, kData %x, csKey %x, data %x, hK %x", kData, csKey, data, hK)
|
|
}
|
|
data = data[common.AddressLength:]
|
|
if len(data) > 0 { // Skip accounts did not exist
|
|
goOn, err = walker(hK, data)
|
|
}
|
|
} else if cmp == 0 {
|
|
goOn, err = walker(k, v)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if goOn {
|
|
if cmp <= 0 {
|
|
k, v, err1 = mainCursor.Next()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
for k != nil && len(k) > common.AddressLength {
|
|
k, v, err1 = mainCursor.Next()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
}
|
|
}
|
|
if cmp >= 0 {
|
|
hK0 := hK
|
|
for hK != nil && (bytes.Equal(hK0, hK) || binary.BigEndian.Uint64(tsEnc) < timestamp) {
|
|
hK, tsEnc, _, hV, err1 = hCursor.Next()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
|
|
}
|