Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return PrunedError when trying to read unavailable historical data #13014

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
18 changes: 18 additions & 0 deletions core/state/history_reader_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package state

import (
"errors"
"fmt"

"github.com/erigontech/erigon-lib/common"
Expand All @@ -25,6 +26,8 @@ import (
"github.com/erigontech/erigon-lib/types/accounts"
)

var PrunedError = errors.New("old data not available due to pruning")

// HistoryReaderV3 Implements StateReader and StateWriter
type HistoryReaderV3 struct {
txNum uint64
Expand All @@ -50,6 +53,21 @@ func (hr *HistoryReaderV3) SetTxNum(txNum uint64) { hr.txNum = txNum }
func (hr *HistoryReaderV3) GetTxNum() uint64 { return hr.txNum }
func (hr *HistoryReaderV3) SetTrace(trace bool) { hr.trace = trace }

// Returns the earliest known txnum in history files for state history
// This is the smallest txNum found across:
//
// - Account history
//
// - Storage history
//
// - Code history
//
// Not considered in the calculation are Commitment history and Receipt history, as
// there are separate functions handling them.
func (hr *HistoryReaderV3) StateHistoryStartFrom() uint64 {
return hr.ttx.StateHistoryStartFrom()
}

func (hr *HistoryReaderV3) ReadSet() map[string]*state.KvList { return nil }
func (hr *HistoryReaderV3) ResetReadSet() {}
func (hr *HistoryReaderV3) DiscardReadList() {}
Expand Down
3 changes: 3 additions & 0 deletions erigon-lib/kv/kv_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ type TemporalTx interface {
Tx
TemporalGetter

// return the earliest known txnum in state history (excluding commitment and receipt history)
StateHistoryStartFrom() uint64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tx/TemporalTx interface - is low-level interface. it must be business-logic agnostic.
"State" - it's something from Ethereum business-dictionary.
TemporalTx must not know - which domains are exist (or may be created in future). It also doesn't know what stored inside each domain (for TemporalTx - it's just some keys/values/timestamps).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, I'll check if I can make it more general, i.e. HistoryStartFrom()


// DomainGetAsOf - state as of given `ts`
// Example: GetAsOf(Account, key, txNum) - retuns account's value before `txNum` transaction changed it
// Means if you want re-execute `txNum` on historical state - do `DomainGetAsOf(key, txNum)` to read state
Expand Down
6 changes: 6 additions & 0 deletions erigon-lib/kv/remotedb/kv_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,12 @@ func (c *remoteCursorDupSort) PrevNoDup() ([]byte, []byte, error) { return c.pre
func (c *remoteCursorDupSort) LastDup() ([]byte, error) { return c.lastDup() }

// Temporal Methods

func (tx *tx) StateHistoryStartFrom() uint64 {
// TODO: not yet implemented, return 0 for now
return 0
}

func (tx *tx) GetAsOf(name kv.Domain, k []byte, ts uint64) (v []byte, ok bool, err error) {
reply, err := tx.db.remoteKV.GetLatest(tx.ctx, &remote.GetLatestReq{TxId: tx.id, Table: name.String(), K: k, Ts: ts})
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions erigon-lib/kv/temporal/kv_temporal.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ func (tx *Tx) Commit() error {
return mdbxTx.Commit()
}

func (tx *Tx) StateHistoryStartFrom() uint64 {
return tx.filesTx.StateHistoryStartFrom()
}

func (tx *Tx) RangeAsOf(name kv.Domain, fromKey, toKey []byte, asOfTs uint64, asc order.By, limit int) (stream.KV, error) {
it, err := tx.filesTx.RangeAsOf(tx.ctx, tx.MdbxTx, name, fromKey, toKey, asOfTs, asc, limit)
if err != nil {
Expand Down
25 changes: 25 additions & 0 deletions erigon-lib/state/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,31 @@ func (a *Aggregator) BuildFilesInBackground(txNum uint64) chan struct{} {
return fin
}

// Gets the txNum where Account, Storage and Code history begins.
// If the node is an archive node all history will be available therefore
// the result will be 0.
//
// For non-archive node old history files get deleted, so this number will vary
// but the goal is to know where the historical data begins
func (ac *AggregatorRoTx) StateHistoryStartFrom() uint64 {
var earliestTxNum uint64 = 0
// get the first txnum where accounts, storage , and code are all available in history files
// This is max(HistoryStart(Accounts), HistoryStart(Storage), HistoryStart(Code))
stateDomainNames := []kv.Domain{kv.AccountsDomain, kv.StorageDomain, kv.CodeDomain}
for _, domainName := range stateDomainNames {
domainStartingTxNum := ac.HistoryStartFrom(domainName)
if domainStartingTxNum > earliestTxNum {
earliestTxNum = domainStartingTxNum
}
}
return earliestTxNum
}

// Returns the first known txNum found in history files of a given domain
func (ac *AggregatorRoTx) HistoryStartFrom(domainName kv.Domain) uint64 {
return ac.d[domainName].HistoryStartFrom()
}

func (ac *AggregatorRoTx) IndexRange(name kv.InvertedIdx, k []byte, fromTs, toTs int, asc order.By, limit int, tx kv.Tx) (timestamps stream.U64, err error) {
switch name {
case kv.AccountsHistoryIdx:
Expand Down
8 changes: 8 additions & 0 deletions erigon-lib/state/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,14 @@ func (dt *DomainRoTx) getFromFiles(filekey []byte, maxTxNum uint64) (v []byte, f
return nil, false, 0, 0, nil
}

// Returns the first txNum from available history
func (dt *DomainRoTx) HistoryStartFrom() uint64 {
if len(dt.ht.files) == 0 {
return 0
}
return dt.ht.files[0].startTxNum
}

func (dt *DomainRoTx) GetAsOfFile(key []byte, txNum uint64) ([]byte, bool, error) {
var v []byte
var foundStep uint64
Expand Down
9 changes: 8 additions & 1 deletion turbo/rpchelper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,14 @@ func CreateHistoryStateReader(tx kv.Tx, txNumsReader rawdbv3.TxNumsReader, block
if err != nil {
return nil, err
}
r.SetTxNum(uint64(int(minTxNum) + txnIndex + /* 1 system txNum in beginning of block */ 1))
txNum := uint64(int(minTxNum) + txnIndex + /* 1 system txNum in beginning of block */ 1)
earliestTxNum := r.StateHistoryStartFrom()
if txNum < earliestTxNum {
// data available only starting from earliestTxNum, throw error to avoid unintended
// consequences of using this StateReader
return r, state.PrunedError
}
r.SetTxNum(txNum)
return r, nil
}

Expand Down
Loading