Commit dd37e619 authored by protolambda's avatar protolambda Committed by GitHub

op-supervisor: return typed pairs of L1/L2 blocks, and support invalidation of...

op-supervisor: return typed pairs of L1/L2 blocks, and support invalidation of fromda entries (#13840)

* op-supervisor: return typed pairs of L1/L2 blocks, and support invalidation of fromda entries

* op-supervisor: support DA db block-invalidation and replacement

* op-supervisor: fix and cover edge-case of replacing a block at later L1 scope
parent 6db0c9d0
...@@ -26,9 +26,9 @@ type LogStorage interface { ...@@ -26,9 +26,9 @@ type LogStorage interface {
SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error
Rewind(newHeadBlockNum uint64) error Rewind(newHead eth.BlockID) error
LatestSealedBlockNum() (n uint64, ok bool) LatestSealedBlock() (id eth.BlockID, ok bool)
// FindSealedBlock finds the requested block by number, to check if it exists, // FindSealedBlock finds the requested block by number, to check if it exists,
// returning the block seal if it was found. // returning the block seal if it was found.
...@@ -51,14 +51,14 @@ type LogStorage interface { ...@@ -51,14 +51,14 @@ type LogStorage interface {
} }
type LocalDerivedFromStorage interface { type LocalDerivedFromStorage interface {
First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) First() (pair types.DerivedBlockSealPair, err error)
Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) Latest() (pair types.DerivedBlockSealPair, err error)
AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error
LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error)
DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error) DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error)
FirstAfter(derivedFrom, derived eth.BlockID) (nextDerivedFrom, nextDerived types.BlockSeal, err error) FirstAfter(derivedFrom, derived eth.BlockID) (next types.DerivedBlockSealPair, err error)
NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.BlockSeal, err error) NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.BlockSeal, err error)
NextDerived(derived eth.BlockID) (derivedFrom types.BlockSeal, nextDerived types.BlockSeal, err error) NextDerived(derived eth.BlockID) (next types.DerivedBlockSealPair, err error)
PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error)
PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error)
} }
...@@ -168,15 +168,15 @@ func (db *ChainsDB) AddCrossUnsafeTracker(chainID eth.ChainID) { ...@@ -168,15 +168,15 @@ func (db *ChainsDB) AddCrossUnsafeTracker(chainID eth.ChainID) {
func (db *ChainsDB) ResumeFromLastSealedBlock() error { func (db *ChainsDB) ResumeFromLastSealedBlock() error {
var result error var result error
db.logDBs.Range(func(chain eth.ChainID, logStore LogStorage) bool { db.logDBs.Range(func(chain eth.ChainID, logStore LogStorage) bool {
headNum, ok := logStore.LatestSealedBlockNum() head, ok := logStore.LatestSealedBlock()
if !ok { if !ok {
// db must be empty, nothing to rewind to // db must be empty, nothing to rewind to
db.logger.Info("Resuming, but found no DB contents", "chain", chain) db.logger.Info("Resuming, but found no DB contents", "chain", chain)
return true return true
} }
db.logger.Info("Resuming, starting from last sealed block", "head", headNum) db.logger.Info("Resuming, starting from last sealed block", "head", head)
if err := logStore.Rewind(headNum); err != nil { if err := logStore.Rewind(head); err != nil {
result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, headNum) result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, head)
return false return false
} }
return true return true
......
...@@ -53,6 +53,8 @@ func NewFromEntryStore(logger log.Logger, m Metrics, store EntryStore) (*DB, err ...@@ -53,6 +53,8 @@ func NewFromEntryStore(logger log.Logger, m Metrics, store EntryStore) (*DB, err
// Rewind to the last entry that was derived from a L1 block with the given block number. // Rewind to the last entry that was derived from a L1 block with the given block number.
func (db *DB) Rewind(derivedFrom uint64) error { func (db *DB) Rewind(derivedFrom uint64) error {
db.rwLock.Lock()
defer db.rwLock.Unlock()
index, _, err := db.lastDerivedAt(derivedFrom) index, _, err := db.lastDerivedAt(derivedFrom)
if err != nil { if err != nil {
return fmt.Errorf("failed to find point to rewind to: %w", err) return fmt.Errorf("failed to find point to rewind to: %w", err)
...@@ -66,29 +68,36 @@ func (db *DB) Rewind(derivedFrom uint64) error { ...@@ -66,29 +68,36 @@ func (db *DB) Rewind(derivedFrom uint64) error {
} }
// First returns the first known values, alike to Latest. // First returns the first known values, alike to Latest.
func (db *DB) First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { func (db *DB) First() (pair types.DerivedBlockSealPair, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
lastIndex := db.store.LastEntryIdx() lastIndex := db.store.LastEntryIdx()
if lastIndex < 0 { if lastIndex < 0 {
return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture return types.DerivedBlockSealPair{}, types.ErrFuture
} }
last, err := db.readAt(0) last, err := db.readAt(0)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to read first derivation data: %w", err) return types.DerivedBlockSealPair{}, fmt.Errorf("failed to read first derivation data: %w", err)
} }
return last.derivedFrom, last.derived, nil return last.sealOrErr()
} }
func (db *DB) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error) { func (db *DB) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
// get the last time this L2 block was seen. // last
_, lastCanonical, err := db.lastDerivedFrom(derived.Number)
if err != nil {
return types.BlockSeal{}, fmt.Errorf("failed to find last derived %d: %w", derived.Number, err)
}
// get the first time this L2 block was seen.
selfIndex, self, err := db.firstDerivedFrom(derived.Number) selfIndex, self, err := db.firstDerivedFrom(derived.Number)
if err != nil { if err != nil {
return types.BlockSeal{}, fmt.Errorf("failed to find derived %d: %w", derived.Number, err) return types.BlockSeal{}, fmt.Errorf("failed to find first derived %d: %w", derived.Number, err)
} }
if self.derived.ID() != derived { // The first entry might not match, since it may have been invalidated with a later L1 scope.
// But the last entry should always match.
if lastCanonical.derived.ID() != derived {
return types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict) return types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict)
} }
if selfIndex == 0 { // genesis block has a zeroed block as parent block if selfIndex == 0 { // genesis block has a zeroed block as parent block
...@@ -104,26 +113,48 @@ func (db *DB) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, ...@@ -104,26 +113,48 @@ func (db *DB) PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal,
// Latest returns the last known values: // Latest returns the last known values:
// derivedFrom: the L1 block that the L2 block is safe for (not necessarily the first, multiple L2 blocks may be derived from the same L1 block). // derivedFrom: the L1 block that the L2 block is safe for (not necessarily the first, multiple L2 blocks may be derived from the same L1 block).
// derived: the L2 block that was derived (not necessarily the first, the L1 block may have been empty and repeated the last safe L2 block). // derived: the L2 block that was derived (not necessarily the first, the L1 block may have been empty and repeated the last safe L2 block).
func (db *DB) Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { // If the last entry is invalidated, this returns a types.ErrAwaitReplacementBlock error.
func (db *DB) Latest() (pair types.DerivedBlockSealPair, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
return db.latest() link, err := db.latest()
if err != nil {
return types.DerivedBlockSealPair{}, err
}
return link.sealOrErr()
}
func (db *DB) Invalidated() (pair types.DerivedBlockSealPair, err error) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
link, err := db.latest()
if err != nil {
return types.DerivedBlockSealPair{}, err
}
if !link.invalidated {
return types.DerivedBlockSealPair{}, fmt.Errorf("last entry %s is not invalidated: %w", link, types.ErrConflict)
}
return types.DerivedBlockSealPair{
DerivedFrom: link.derivedFrom,
Derived: link.derived,
}, nil
} }
// latest is like Latest, but without lock, for internal use. // latest is like Latest, but without lock, for internal use.
func (db *DB) latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) { func (db *DB) latest() (link LinkEntry, err error) {
lastIndex := db.store.LastEntryIdx() lastIndex := db.store.LastEntryIdx()
if lastIndex < 0 { if lastIndex < 0 {
return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture return LinkEntry{}, types.ErrFuture
} }
last, err := db.readAt(lastIndex) last, err := db.readAt(lastIndex)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to read last derivation data: %w", err) return LinkEntry{}, fmt.Errorf("failed to read last derivation data: %w", err)
} }
return last.derivedFrom, last.derived, nil return last, nil
} }
// LastDerivedAt returns the last L2 block derived from the given L1 block. // LastDerivedAt returns the last L2 block derived from the given L1 block.
// This may return types.ErrAwaitReplacementBlock if the entry was invalidated and needs replacement.
func (db *DB) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error) { func (db *DB) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
...@@ -135,26 +166,30 @@ func (db *DB) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, e ...@@ -135,26 +166,30 @@ func (db *DB) LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, e
return types.BlockSeal{}, fmt.Errorf("searched for last derived-from %s but found %s: %w", return types.BlockSeal{}, fmt.Errorf("searched for last derived-from %s but found %s: %w",
derivedFrom, link.derivedFrom, types.ErrConflict) derivedFrom, link.derivedFrom, types.ErrConflict)
} }
if link.invalidated {
return types.BlockSeal{}, types.ErrAwaitReplacementBlock
}
return link.derived, nil return link.derived, nil
} }
// NextDerived finds the next L2 block after derived, and what it was derived from // NextDerived finds the next L2 block after derived, and what it was derived from.
func (db *DB) NextDerived(derived eth.BlockID) (derivedFrom types.BlockSeal, nextDerived types.BlockSeal, err error) { // This may return types.ErrAwaitReplacementBlock if the entry was invalidated and needs replacement.
func (db *DB) NextDerived(derived eth.BlockID) (pair types.DerivedBlockSealPair, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
// get the last time this L2 block was seen. // get the last time this L2 block was seen.
selfIndex, self, err := db.lastDerivedFrom(derived.Number) selfIndex, self, err := db.lastDerivedFrom(derived.Number)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("failed to find derived %d: %w", derived.Number, err) return types.DerivedBlockSealPair{}, fmt.Errorf("failed to find derived %d: %w", derived.Number, err)
} }
if self.derived.ID() != derived { if self.derived.ID() != derived {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict) return types.DerivedBlockSealPair{}, fmt.Errorf("found %s, but expected %s: %w", self.derived, derived, types.ErrConflict)
} }
next, err := db.readAt(selfIndex + 1) next, err := db.readAt(selfIndex + 1)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("cannot find next derived after %s: %w", derived, err) return types.DerivedBlockSealPair{}, fmt.Errorf("cannot find next derived after %s: %w", derived, err)
} }
return next.derivedFrom, next.derived, nil return next.sealOrErr()
} }
// DerivedFrom determines where a L2 block was first derived from. // DerivedFrom determines where a L2 block was first derived from.
...@@ -176,6 +211,10 @@ func (db *DB) DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err ...@@ -176,6 +211,10 @@ func (db *DB) DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err
func (db *DB) PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) { func (db *DB) PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
return db.previousDerivedFrom(derivedFrom)
}
func (db *DB) previousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error) {
// get the last time this L1 block was seen. // get the last time this L1 block was seen.
selfIndex, self, err := db.firstDerivedAt(derivedFrom.Number) selfIndex, self, err := db.firstDerivedAt(derivedFrom.Number)
if err != nil { if err != nil {
...@@ -219,25 +258,26 @@ func (db *DB) NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.Bl ...@@ -219,25 +258,26 @@ func (db *DB) NextDerivedFrom(derivedFrom eth.BlockID) (nextDerivedFrom types.Bl
} }
// FirstAfter determines the next entry after the given pair of derivedFrom, derived. // FirstAfter determines the next entry after the given pair of derivedFrom, derived.
// Either one or both of the two entries will be an increment by 1 // Either one or both of the two entries will be an increment by 1.
func (db *DB) FirstAfter(derivedFrom, derived eth.BlockID) (nextDerivedFrom, nextDerived types.BlockSeal, err error) { // This may return types.ErrAwaitReplacementBlock if the entry was invalidated and needs replacement.
func (db *DB) FirstAfter(derivedFrom, derived eth.BlockID) (pair types.DerivedBlockSealPair, err error) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
selfIndex, selfLink, err := db.lookup(derivedFrom.Number, derived.Number) selfIndex, selfLink, err := db.lookup(derivedFrom.Number, derived.Number)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, err return types.DerivedBlockSealPair{}, err
} }
if selfLink.derivedFrom.ID() != derivedFrom { if selfLink.derivedFrom.ID() != derivedFrom {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("DB has derived-from %s but expected %s: %w", selfLink.derivedFrom, derivedFrom, types.ErrConflict) return types.DerivedBlockSealPair{}, fmt.Errorf("DB has derived-from %s but expected %s: %w", selfLink.derivedFrom, derivedFrom, types.ErrConflict)
} }
if selfLink.derived.ID() != derived { if selfLink.derived.ID() != derived {
return types.BlockSeal{}, types.BlockSeal{}, fmt.Errorf("DB has derived %s but expected %s: %w", selfLink.derived, derived, types.ErrConflict) return types.DerivedBlockSealPair{}, fmt.Errorf("DB has derived %s but expected %s: %w", selfLink.derived, derived, types.ErrConflict)
} }
next, err := db.readAt(selfIndex + 1) next, err := db.readAt(selfIndex + 1)
if err != nil { if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, err return types.DerivedBlockSealPair{}, err
} }
return next.derivedFrom, next.derived, nil return next.sealOrErr()
} }
func (db *DB) lastDerivedFrom(derived uint64) (entrydb.EntryIdx, LinkEntry, error) { func (db *DB) lastDerivedFrom(derived uint64) (entrydb.EntryIdx, LinkEntry, error) {
......
...@@ -72,10 +72,10 @@ func TestEmptyDB(t *testing.T) { ...@@ -72,10 +72,10 @@ func TestEmptyDB(t *testing.T) {
runDBTest(t, runDBTest(t,
func(t *testing.T, db *DB, m *stubMetrics) {}, func(t *testing.T, db *DB, m *stubMetrics) {},
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
_, _, err := db.Latest() _, err := db.Latest()
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, _, err = db.First() _, err = db.First()
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, err = db.LastDerivedAt(eth.BlockID{}) _, err = db.LastDerivedAt(eth.BlockID{})
...@@ -87,7 +87,7 @@ func TestEmptyDB(t *testing.T) { ...@@ -87,7 +87,7 @@ func TestEmptyDB(t *testing.T) {
_, err = db.PreviousDerived(eth.BlockID{}) _, err = db.PreviousDerived(eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, _, err = db.NextDerived(eth.BlockID{}) _, err = db.NextDerived(eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, err = db.PreviousDerivedFrom(eth.BlockID{}) _, err = db.PreviousDerivedFrom(eth.BlockID{})
...@@ -96,7 +96,7 @@ func TestEmptyDB(t *testing.T) { ...@@ -96,7 +96,7 @@ func TestEmptyDB(t *testing.T) {
_, err = db.NextDerivedFrom(eth.BlockID{}) _, err = db.NextDerivedFrom(eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, _, err = db.FirstAfter(eth.BlockID{}, eth.BlockID{}) _, err = db.FirstAfter(eth.BlockID{}, eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
}) })
} }
...@@ -139,23 +139,23 @@ func TestSingleEntryDB(t *testing.T) { ...@@ -139,23 +139,23 @@ func TestSingleEntryDB(t *testing.T) {
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
// First // First
derivedFrom, derived, err := db.First() pair, err := db.First()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom) require.Equal(t, expectedDerivedFrom, pair.DerivedFrom)
require.Equal(t, expectedDerived, derived) require.Equal(t, expectedDerived, pair.Derived)
// Latest // Latest
derivedFrom, derived, err = db.Latest() pair, err = db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom) require.Equal(t, expectedDerivedFrom, pair.DerivedFrom)
require.Equal(t, expectedDerived, derived) require.Equal(t, expectedDerived, pair.Derived)
// FirstAfter Latest // FirstAfter Latest
_, _, err = db.FirstAfter(derivedFrom.ID(), derived.ID()) _, err = db.FirstAfter(pair.DerivedFrom.ID(), pair.Derived.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
// LastDerivedAt // LastDerivedAt
derived, err = db.LastDerivedAt(expectedDerivedFrom.ID()) derived, err := db.LastDerivedAt(expectedDerivedFrom.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedDerived, derived) require.Equal(t, expectedDerived, derived)
...@@ -164,13 +164,13 @@ func TestSingleEntryDB(t *testing.T) { ...@@ -164,13 +164,13 @@ func TestSingleEntryDB(t *testing.T) {
require.ErrorIs(t, err, types.ErrConflict) require.ErrorIs(t, err, types.ErrConflict)
// FirstAfter with a non-existent block (derived and derivedFrom) // FirstAfter with a non-existent block (derived and derivedFrom)
_, _, err = db.FirstAfter(eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerivedFrom.Number}, expectedDerived.ID()) _, err = db.FirstAfter(eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerivedFrom.Number}, expectedDerived.ID())
require.ErrorIs(t, err, types.ErrConflict) require.ErrorIs(t, err, types.ErrConflict)
_, _, err = db.FirstAfter(expectedDerivedFrom.ID(), eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerived.Number}) _, err = db.FirstAfter(expectedDerivedFrom.ID(), eth.BlockID{Hash: common.Hash{0xaa}, Number: expectedDerived.Number})
require.ErrorIs(t, err, types.ErrConflict) require.ErrorIs(t, err, types.ErrConflict)
// DerivedFrom // DerivedFrom
derivedFrom, err = db.DerivedFrom(expectedDerived.ID()) derivedFrom, err := db.DerivedFrom(expectedDerived.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom) require.Equal(t, expectedDerivedFrom, derivedFrom)
...@@ -189,7 +189,7 @@ func TestSingleEntryDB(t *testing.T) { ...@@ -189,7 +189,7 @@ func TestSingleEntryDB(t *testing.T) {
require.Equal(t, types.BlockSeal{}, prev, "zeroed seal before first entry") require.Equal(t, types.BlockSeal{}, prev, "zeroed seal before first entry")
// NextDerived // NextDerived
_, _, err = db.NextDerived(expectedDerived.ID()) _, err = db.NextDerived(expectedDerived.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
// NextDerivedFrom // NextDerivedFrom
...@@ -197,7 +197,7 @@ func TestSingleEntryDB(t *testing.T) { ...@@ -197,7 +197,7 @@ func TestSingleEntryDB(t *testing.T) {
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
// FirstAfter // FirstAfter
_, _, err = db.FirstAfter(expectedDerivedFrom.ID(), expectedDerived.ID()) _, err = db.FirstAfter(expectedDerivedFrom.ID(), expectedDerived.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
}) })
} }
...@@ -212,7 +212,7 @@ func TestGap(t *testing.T) { ...@@ -212,7 +212,7 @@ func TestGap(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(expectedDerivedFrom, mockL1(0).Hash), toRef(expectedDerived, mockL2(0).Hash))) require.NoError(t, db.AddDerived(toRef(expectedDerivedFrom, mockL1(0).Hash), toRef(expectedDerived, mockL2(0).Hash)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
_, _, err := db.NextDerived(mockL2(0).ID()) _, err := db.NextDerived(mockL2(0).ID())
require.ErrorIs(t, err, types.ErrSkipped) require.ErrorIs(t, err, types.ErrSkipped)
_, err = db.NextDerivedFrom(mockL1(0).ID()) _, err = db.NextDerivedFrom(mockL1(0).ID())
...@@ -235,24 +235,24 @@ func TestThreeEntryDB(t *testing.T) { ...@@ -235,24 +235,24 @@ func TestThreeEntryDB(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block2, l2Block1.Hash))) require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) { }, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, pair.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, pair.Derived)
derivedFrom, derived, err = db.First() pair, err = db.First()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block0, derivedFrom) require.Equal(t, l1Block0, pair.DerivedFrom)
require.Equal(t, l2Block0, derived) require.Equal(t, l2Block0, pair.Derived)
derived, err = db.LastDerivedAt(l1Block2.ID()) derived, err := db.LastDerivedAt(l1Block2.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, derived)
_, err = db.LastDerivedAt(eth.BlockID{Hash: common.Hash{0xaa}, Number: l1Block2.Number}) _, err = db.LastDerivedAt(eth.BlockID{Hash: common.Hash{0xaa}, Number: l1Block2.Number})
require.ErrorIs(t, err, types.ErrConflict) require.ErrorIs(t, err, types.ErrConflict)
derivedFrom, err = db.DerivedFrom(l2Block2.ID()) derivedFrom, err := db.DerivedFrom(l2Block2.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, derivedFrom)
...@@ -287,17 +287,17 @@ func TestThreeEntryDB(t *testing.T) { ...@@ -287,17 +287,17 @@ func TestThreeEntryDB(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, next.Derived)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, next.Derived)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, next.DerivedFrom)
_, _, err = db.NextDerived(l2Block2.ID()) _, err = db.NextDerived(l2Block2.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block0.ID()) derivedFrom, err = db.PreviousDerivedFrom(l1Block0.ID())
...@@ -323,18 +323,18 @@ func TestThreeEntryDB(t *testing.T) { ...@@ -323,18 +323,18 @@ func TestThreeEntryDB(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block2.ID()) _, err = db.NextDerivedFrom(l1Block2.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
_, _, err = db.FirstAfter(l1Block2.ID(), l2Block2.ID()) _, err = db.FirstAfter(l1Block2.ID(), l2Block2.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, derived, err = db.FirstAfter(l1Block0.ID(), l2Block0.ID()) next, err = db.FirstAfter(l1Block0.ID(), l2Block0.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, next.Derived)
derivedFrom, derived, err = db.FirstAfter(l1Block1.ID(), l2Block1.ID()) next, err = db.FirstAfter(l1Block1.ID(), l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, next.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, next.Derived)
}) })
} }
...@@ -364,17 +364,17 @@ func TestFastL2Batcher(t *testing.T) { ...@@ -364,17 +364,17 @@ func TestFastL2Batcher(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block5, l2Block4.Hash))) require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block5, l2Block4.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) { }, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, pair.DerivedFrom)
require.Equal(t, l2Block5, derived) require.Equal(t, l2Block5, pair.Derived)
derived, err = db.LastDerivedAt(l1Block2.ID()) derived, err := db.LastDerivedAt(l1Block2.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block5, derived) require.Equal(t, l2Block5, derived)
// test what tip was derived from // test what tip was derived from
derivedFrom, err = db.DerivedFrom(l2Block5.ID()) derivedFrom, err := db.DerivedFrom(l2Block5.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) require.Equal(t, l1Block2, derivedFrom)
...@@ -406,27 +406,27 @@ func TestFastL2Batcher(t *testing.T) { ...@@ -406,27 +406,27 @@ func TestFastL2Batcher(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block0, derived) require.Equal(t, l2Block0, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, next.Derived)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, next.Derived)
derivedFrom, derived, err = db.NextDerived(l2Block2.ID()) next, err = db.NextDerived(l2Block2.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block3, derived) require.Equal(t, l2Block3, next.Derived)
derivedFrom, derived, err = db.NextDerived(l2Block3.ID()) next, err = db.NextDerived(l2Block3.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block4, derived) require.Equal(t, l2Block4, next.Derived)
derivedFrom, derived, err = db.NextDerived(l2Block4.ID()) next, err = db.NextDerived(l2Block4.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) // derived from later L1 block require.Equal(t, l1Block2, next.DerivedFrom) // derived from later L1 block
require.Equal(t, l2Block5, derived) require.Equal(t, l2Block5, next.Derived)
_, _, err = db.NextDerived(l2Block5.ID()) _, err = db.NextDerived(l2Block5.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID()) derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID())
...@@ -445,10 +445,10 @@ func TestFastL2Batcher(t *testing.T) { ...@@ -445,10 +445,10 @@ func TestFastL2Batcher(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block2.ID()) _, err = db.NextDerivedFrom(l1Block2.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, derived, err = db.FirstAfter(l1Block1.ID(), l2Block2.ID()) next, err = db.FirstAfter(l1Block1.ID(), l2Block2.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) // no increment in L1 yet, the next after is L2 block 3 require.Equal(t, l1Block1, next.DerivedFrom) // no increment in L1 yet, the next after is L2 block 3
require.Equal(t, l2Block3, derived) require.Equal(t, l2Block3, next.Derived)
}) })
} }
...@@ -478,13 +478,13 @@ func TestSlowL2Batcher(t *testing.T) { ...@@ -478,13 +478,13 @@ func TestSlowL2Batcher(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash))) require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) { }, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom) require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, pair.Derived)
// test what we last derived at the tip // test what we last derived at the tip
derived, err = db.LastDerivedAt(l1Block5.ID()) derived, err := db.LastDerivedAt(l1Block5.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, derived)
...@@ -496,7 +496,7 @@ func TestSlowL2Batcher(t *testing.T) { ...@@ -496,7 +496,7 @@ func TestSlowL2Batcher(t *testing.T) {
} }
// test that the first L1 counts, not the ones that repeat the L2 info // test that the first L1 counts, not the ones that repeat the L2 info
derivedFrom, err = db.DerivedFrom(l2Block1.ID()) derivedFrom, err := db.DerivedFrom(l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, derivedFrom)
...@@ -507,15 +507,15 @@ func TestSlowL2Batcher(t *testing.T) { ...@@ -507,15 +507,15 @@ func TestSlowL2Batcher(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l2Block0, derived) require.Equal(t, l2Block0, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID()) next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, next.Derived)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID()) next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom) require.Equal(t, l1Block5, next.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, next.Derived)
_, _, err = db.NextDerived(l2Block2.ID()) _, err = db.NextDerived(l2Block2.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block5.ID()) derivedFrom, err = db.PreviousDerivedFrom(l1Block5.ID())
...@@ -549,10 +549,10 @@ func TestSlowL2Batcher(t *testing.T) { ...@@ -549,10 +549,10 @@ func TestSlowL2Batcher(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block5.ID()) _, err = db.NextDerivedFrom(l1Block5.ID())
require.ErrorIs(t, err, types.ErrFuture) require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, derived, err = db.FirstAfter(l1Block2.ID(), l2Block1.ID()) next, err = db.FirstAfter(l1Block2.ID(), l2Block1.ID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block3, derivedFrom) require.Equal(t, l1Block3, next.DerivedFrom)
require.Equal(t, l2Block1, derived) // no increment in L2 yet, the next after is L1 block 3 require.Equal(t, l2Block1, next.Derived) // no increment in L2 yet, the next after is L1 block 3
}) })
} }
...@@ -586,34 +586,34 @@ func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) { ...@@ -586,34 +586,34 @@ func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) {
rng := rand.New(rand.NewSource(1234)) rng := rand.New(rand.NewSource(1234))
// Insert 1000 randomly generated entries, derived at random bumps in L1 // Insert 1000 randomly generated entries, derived at random bumps in L1
for i := uint64(0); i < 1000; i++ { for i := uint64(0); i < 1000; i++ {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
switch rng.Intn(3) { switch rng.Intn(3) {
case 0: // bump L1 case 0: // bump L1
derivedFrom = mockL1(derivedFrom.Number + 1) pair.DerivedFrom = mockL1(pair.DerivedFrom.Number + 1)
case 1: // bump L2 case 1: // bump L2
derived = mockL2(derived.Number + 1) pair.Derived = mockL2(pair.Derived.Number + 1)
case 2: // bump both case 2: // bump both
derivedFrom = mockL1(derivedFrom.Number + 1) pair.DerivedFrom = mockL1(pair.DerivedFrom.Number + 1)
derived = mockL2(derived.Number + 1) pair.Derived = mockL2(pair.Derived.Number + 1)
} }
derivedFromRef := toRef(derivedFrom, mockL1(derivedFrom.Number-1).Hash) derivedFromRef := toRef(pair.DerivedFrom, mockL1(pair.DerivedFrom.Number-1).Hash)
derivedRef := toRef(derived, mockL2(derived.Number-1).Hash) derivedRef := toRef(pair.Derived, mockL2(pair.Derived.Number-1).Hash)
lastDerived[derivedFromRef.ID()] = derived lastDerived[derivedFromRef.ID()] = pair.Derived
if _, ok := firstDerivedFrom[derivedRef.ID()]; !ok { if _, ok := firstDerivedFrom[derivedRef.ID()]; !ok {
firstDerivedFrom[derivedRef.ID()] = derivedFrom firstDerivedFrom[derivedRef.ID()] = pair.DerivedFrom
} }
require.NoError(t, db.AddDerived(derivedFromRef, derivedRef)) require.NoError(t, db.AddDerived(derivedFromRef, derivedRef))
} }
}, func(t *testing.T, db *DB, m *stubMetrics) { }, func(t *testing.T, db *DB, m *stubMetrics) {
// Now assert we can find what they are all derived from, and match the expectations. // Now assert we can find what they are all derived from, and match the expectations.
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.NotZero(t, derivedFrom.Number-offsetL1) require.NotZero(t, pair.DerivedFrom.Number-offsetL1)
require.NotZero(t, derived.Number-offsetL2) require.NotZero(t, pair.Derived.Number-offsetL2)
for i := offsetL1; i <= derivedFrom.Number; i++ { for i := offsetL1; i <= pair.DerivedFrom.Number; i++ {
l1ID := mockL1(i).ID() l1ID := mockL1(i).ID()
derived, err := db.LastDerivedAt(l1ID) derived, err := db.LastDerivedAt(l1ID)
require.NoError(t, err) require.NoError(t, err)
...@@ -621,7 +621,7 @@ func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) { ...@@ -621,7 +621,7 @@ func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) {
require.Equal(t, lastDerived[l1ID], derived) require.Equal(t, lastDerived[l1ID], derived)
} }
for i := offsetL2; i <= derived.Number; i++ { for i := offsetL2; i <= pair.Derived.Number; i++ {
l2ID := mockL2(i).ID() l2ID := mockL2(i).ID()
derivedFrom, err := db.DerivedFrom(l2ID) derivedFrom, err := db.DerivedFrom(l2ID)
require.NoError(t, err) require.NoError(t, err)
...@@ -673,42 +673,187 @@ func TestRewind(t *testing.T) { ...@@ -673,42 +673,187 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash))) require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) { }, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom) require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, pair.Derived)
// Rewind to the future // Rewind to the future
require.ErrorIs(t, db.Rewind(6), types.ErrFuture) require.ErrorIs(t, db.Rewind(6), types.ErrFuture)
// Rewind to the exact block we're at // Rewind to the exact block we're at
require.NoError(t, db.Rewind(l1Block5.Number)) require.NoError(t, db.Rewind(l1Block5.Number))
derivedFrom, derived, err = db.Latest() pair, err = db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom) require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, derived) require.Equal(t, l2Block2, pair.Derived)
// Now rewind to L1 block 3 (inclusive). // Now rewind to L1 block 3 (inclusive).
require.NoError(t, db.Rewind(l1Block3.Number)) require.NoError(t, db.Rewind(l1Block3.Number))
// See if we find consistent data // See if we find consistent data
derivedFrom, derived, err = db.Latest() pair, err = db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block3, derivedFrom) require.Equal(t, l1Block3, pair.DerivedFrom)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, pair.Derived)
// Rewind further to L1 block 1 (inclusive). // Rewind further to L1 block 1 (inclusive).
require.NoError(t, db.Rewind(l1Block1.Number)) require.NoError(t, db.Rewind(l1Block1.Number))
derivedFrom, derived, err = db.Latest() pair, err = db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom) require.Equal(t, l1Block1, pair.DerivedFrom)
require.Equal(t, l2Block1, derived) require.Equal(t, l2Block1, pair.Derived)
// Rewind further to L1 block 0 (inclusive). // Rewind further to L1 block 0 (inclusive).
require.NoError(t, db.Rewind(l1Block0.Number)) require.NoError(t, db.Rewind(l1Block0.Number))
derivedFrom, derived, err = db.Latest() pair, err = db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, l1Block0, derivedFrom) require.Equal(t, l1Block0, pair.DerivedFrom)
require.Equal(t, l2Block0, derived) require.Equal(t, l2Block0, pair.Derived)
})
}
func TestInvalidateAndReplace(t *testing.T) {
l1Block0 := mockL1(0)
l1Block1 := mockL1(1)
l1Ref0 := toRef(l1Block0, common.Hash{})
l1Ref1 := toRef(l1Block1, l1Block0.Hash)
l1Ref2 := toRef(l1Block1, l1Block0.Hash)
l1Ref3 := toRef(l1Block1, l1Block0.Hash)
l2Block0 := mockL2(0)
l2Block1 := mockL2(1)
l2Block2 := mockL2(2)
l2Block3 := mockL2(3)
l2Ref1 := toRef(l2Block1, l2Block0.Hash)
l2Ref2 := toRef(l2Block2, l2Block1.Hash)
l2Ref3 := toRef(l2Block3, l2Block2.Hash)
runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {
require.NoError(t, db.AddDerived(l1Ref0, toRef(l2Block0, common.Hash{})))
require.NoError(t, db.AddDerived(l1Ref1, l2Ref1))
require.NoError(t, db.AddDerived(l1Ref2, l2Ref2))
require.NoError(t, db.AddDerived(l1Ref3, l2Ref3))
}, func(t *testing.T, db *DB, m *stubMetrics) {
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l2Ref3.ID(), pair.Derived.ID())
require.Equal(t, l1Block1.ID(), pair.DerivedFrom.ID())
_, err = db.Invalidated()
require.ErrorIs(t, err, types.ErrConflict)
invalidated := types.DerivedBlockRefPair{
DerivedFrom: l1Ref1,
Derived: l2Ref2,
}
require.NoError(t, db.RewindAndInvalidate(invalidated))
_, err = db.Latest()
require.ErrorIs(t, err, types.ErrAwaitReplacementBlock)
pair, err = db.Invalidated()
require.NoError(t, err)
require.Equal(t, invalidated.DerivedFrom.ID(), pair.DerivedFrom.ID())
require.Equal(t, invalidated.Derived.ID(), pair.Derived.ID())
replacement := l2Ref2
replacement.Hash = common.Hash{0xff, 0xff, 0xff}
require.NotEqual(t, l2Ref2.Hash, replacement.Hash) // different L2 block as replacement
require.NoError(t, db.ReplaceInvalidatedBlock(replacement, invalidated.Derived.Hash))
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, replacement.ID(), pair.Derived.ID())
require.Equal(t, l1Block1.ID(), pair.DerivedFrom.ID())
})
}
// TestInvalidateAndReplaceNonFirst covers an edge-case where we invalidate an L2 block,
// but only at a later L1 scope, after the L2 block has already been derived from previous L1 blocks.
// At previous L1 blocks, the original L2 block is still needed, for accurate local-safe information,
// as future L1 data does not retroactively change the interpretation of past data within that past scope.
func TestInvalidateAndReplaceNonFirst(t *testing.T) {
l1Block0 := mockL1(0)
l1Block1 := mockL1(1)
l1Block2 := mockL1(2)
l1Ref0 := toRef(l1Block0, common.Hash{})
l1Ref1 := toRef(l1Block1, l1Block0.Hash)
l1Ref2 := toRef(l1Block2, l1Block1.Hash)
l2Block0 := mockL2(0)
l2Block1 := mockL2(1)
l2Block2 := mockL2(2)
l2Block3 := mockL2(3)
l2Block4 := mockL2(4)
l2Ref1 := toRef(l2Block1, l2Block0.Hash)
l2Ref2 := toRef(l2Block2, l2Block1.Hash)
l2Ref3 := toRef(l2Block3, l2Block2.Hash)
l2Ref4 := toRef(l2Block4, l2Block3.Hash)
runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {
require.NoError(t, db.AddDerived(l1Ref0, toRef(l2Block0, common.Hash{})))
require.NoError(t, db.AddDerived(l1Ref1, l2Ref1))
require.NoError(t, db.AddDerived(l1Ref1, l2Ref2))
require.NoError(t, db.AddDerived(l1Ref1, l2Ref3))
// note the repeat of the L2 block with the bump in L1 scope
require.NoError(t, db.AddDerived(l1Ref2, l2Ref3)) // to be invalidated and replaced
require.NoError(t, db.AddDerived(l1Ref2, l2Ref4))
}, func(t *testing.T, db *DB, m *stubMetrics) {
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l2Ref4.ID(), pair.Derived.ID())
require.Equal(t, l1Block2.ID(), pair.DerivedFrom.ID())
_, err = db.Invalidated()
require.ErrorIs(t, err, types.ErrConflict)
invalidated := types.DerivedBlockRefPair{
DerivedFrom: l1Ref2,
Derived: l2Ref3,
}
require.NoError(t, db.RewindAndInvalidate(invalidated))
_, err = db.Latest()
require.ErrorIs(t, err, types.ErrAwaitReplacementBlock)
pair, err = db.Invalidated()
require.NoError(t, err)
require.Equal(t, invalidated.DerivedFrom.ID(), pair.DerivedFrom.ID())
require.Equal(t, invalidated.Derived.ID(), pair.Derived.ID())
replacement := l2Ref3
replacement.Hash = common.Hash{0xff, 0xff, 0xff}
require.NotEqual(t, l2Ref3.Hash, replacement.Hash) // different L2 block as replacement
require.NoError(t, db.ReplaceInvalidatedBlock(replacement, invalidated.Derived.Hash))
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, replacement.ID(), pair.Derived.ID())
require.Equal(t, l1Block2.ID(), pair.DerivedFrom.ID())
// The L2 block before the replacement should point to 2
prev, err := db.PreviousDerived(replacement.ID())
require.NoError(t, err)
require.Equal(t, l2Ref2.ID(), prev.ID())
lastFrom1, err := db.LastDerivedAt(l1Block1.ID())
require.NoError(t, err)
// while invalidated, at this point in L1, it was still the local-safe block
require.Equal(t, l2Ref3.ID(), lastFrom1.ID())
// This should point to the original, since we traverse based on L1 scope
entryBlock3, err := db.FirstAfter(l1Block1.ID(), l2Ref2.ID())
require.NoError(t, err)
require.Equal(t, l2Ref3.ID(), entryBlock3.Derived.ID())
require.Equal(t, l1Block1.ID(), entryBlock3.DerivedFrom.ID())
// And then find the replacement, once we traverse further
entryBlockRepl, err := db.FirstAfter(l1Block1.ID(), l2Ref3.ID())
require.NoError(t, err)
require.Equal(t, replacement.ID(), entryBlockRepl.Derived.ID())
require.Equal(t, l1Block2.ID(), entryBlockRepl.DerivedFrom.ID())
}) })
} }
...@@ -20,12 +20,15 @@ type EntryType uint8 ...@@ -20,12 +20,15 @@ type EntryType uint8
const ( const (
DerivedFromV0 EntryType = 0 DerivedFromV0 EntryType = 0
InvalidatedFromV0 EntryType = 1
) )
func (s EntryType) String() string { func (s EntryType) String() string {
switch s { switch s {
case DerivedFromV0: case DerivedFromV0:
return "v0" return "derivedFromV0"
case InvalidatedFromV0:
return "invalidatedFromV0"
default: default:
return fmt.Sprintf("unknown(%d)", uint8(s)) return fmt.Sprintf("unknown(%d)", uint8(s))
} }
...@@ -45,22 +48,31 @@ func (EntryBinary) EntrySize() int { ...@@ -45,22 +48,31 @@ func (EntryBinary) EntrySize() int {
return EntrySize return EntrySize
} }
// LinkEntry is a DerivedFromV0 or a InvalidatedFromV0 kind
type LinkEntry struct { type LinkEntry struct {
derivedFrom types.BlockSeal derivedFrom types.BlockSeal
derived types.BlockSeal derived types.BlockSeal
// when it exists as local-safe, but cannot be cross-safe.
// If false: this link is a DerivedFromV0
// If true: this link is a InvalidatedFromV0
invalidated bool
} }
func (d LinkEntry) String() string { func (d LinkEntry) String() string {
return fmt.Sprintf("LinkEntry(derivedFrom: %s, derived: %s)", d.derivedFrom, d.derived) return fmt.Sprintf("LinkEntry(derivedFrom: %s, derived: %s, invalidated: %v)", d.derivedFrom, d.derived, d.invalidated)
} }
func (d *LinkEntry) decode(e Entry) error { func (d *LinkEntry) decode(e Entry) error {
if e.Type() != DerivedFromV0 { if t := e.Type(); t != DerivedFromV0 && t != InvalidatedFromV0 {
return fmt.Errorf("%w: unexpected entry type: %s", types.ErrDataCorruption, e.Type()) return fmt.Errorf("%w: unexpected entry type: %s", types.ErrDataCorruption, e.Type())
} }
if [3]byte(e[1:4]) != ([3]byte{}) { if [3]byte(e[1:4]) != ([3]byte{}) {
return fmt.Errorf("%w: expected empty data, to pad entry size to round number: %x", types.ErrDataCorruption, e[1:4]) return fmt.Errorf("%w: expected empty data, to pad entry size to round number: %x", types.ErrDataCorruption, e[1:4])
} }
d.invalidated = e.Type() == InvalidatedFromV0
// Format:
// l1-number(8) l1-timestamp(8) l2-number(8) l2-timestamp(8) l1-hash(32) l2-hash(32)
// Note: attributes are ordered for lexical sorting to nicely match chronological sorting.
offset := 4 offset := 4
d.derivedFrom.Number = binary.BigEndian.Uint64(e[offset : offset+8]) d.derivedFrom.Number = binary.BigEndian.Uint64(e[offset : offset+8])
offset += 8 offset += 8
...@@ -78,7 +90,11 @@ func (d *LinkEntry) decode(e Entry) error { ...@@ -78,7 +90,11 @@ func (d *LinkEntry) decode(e Entry) error {
func (d *LinkEntry) encode() Entry { func (d *LinkEntry) encode() Entry {
var out Entry var out Entry
if d.invalidated {
out[0] = uint8(InvalidatedFromV0)
} else {
out[0] = uint8(DerivedFromV0) out[0] = uint8(DerivedFromV0)
}
offset := 4 offset := 4
binary.BigEndian.PutUint64(out[offset:offset+8], d.derivedFrom.Number) binary.BigEndian.PutUint64(out[offset:offset+8], d.derivedFrom.Number)
offset += 8 offset += 8
...@@ -93,3 +109,13 @@ func (d *LinkEntry) encode() Entry { ...@@ -93,3 +109,13 @@ func (d *LinkEntry) encode() Entry {
copy(out[offset:offset+32], d.derived.Hash[:]) copy(out[offset:offset+32], d.derived.Hash[:])
return out return out
} }
func (d *LinkEntry) sealOrErr() (types.DerivedBlockSealPair, error) {
if d.invalidated {
return types.DerivedBlockSealPair{}, types.ErrAwaitReplacementBlock
}
return types.DerivedBlockSealPair{
DerivedFrom: d.derivedFrom,
Derived: d.derived,
}, nil
}
...@@ -3,6 +3,8 @@ package fromda ...@@ -3,6 +3,8 @@ package fromda
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
...@@ -10,9 +12,86 @@ import ( ...@@ -10,9 +12,86 @@ import (
func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
db.rwLock.Lock() db.rwLock.Lock()
defer db.rwLock.Unlock() defer db.rwLock.Unlock()
return db.addLink(derivedFrom, derived, common.Hash{})
}
// If we don't have any entries yet, allow any block to start things off // ReplaceInvalidatedBlock replaces the current Invalidated block with the given replacement.
if db.store.Size() == 0 { // The to-be invalidated hash must be provided for consistency checks.
func (db *DB) ReplaceInvalidatedBlock(replacementDerived eth.BlockRef, invalidated common.Hash) error {
db.rwLock.Lock()
defer db.rwLock.Unlock()
// We take the last occurrence. This is where it started to be considered invalid,
// and where we thus stopped building additional entries for it.
lastIndex := db.store.LastEntryIdx()
if lastIndex < 0 {
return types.ErrFuture
}
last, err := db.readAt(lastIndex)
if err != nil {
return fmt.Errorf("failed to read last derivation data: %w", err)
}
if !last.invalidated {
return fmt.Errorf("cannot replace block %d, that was not invalidated, with block %s: %w", last.derived, replacementDerived, types.ErrConflict)
}
if last.derived.Hash != invalidated {
return fmt.Errorf("cannot replace invalidated %s, DB contains %s: %w", invalidated, last.derived, types.ErrConflict)
}
// Find the parent-block of derived-from.
// We need this to build a block-ref, so the DB can be consistency-checked when the next entry is added.
// There is always one, since the first entry in the DB should never be an invalidated one.
prevDerivedFrom, err := db.previousDerivedFrom(last.derivedFrom.ID())
if err != nil {
return err
}
// Remove the invalidated placeholder and everything after
err = db.store.Truncate(lastIndex - 1)
if err != nil {
return err
}
replacement := types.DerivedBlockRefPair{
DerivedFrom: last.derivedFrom.ForceWithParent(prevDerivedFrom.ID()),
Derived: replacementDerived,
}
// Insert the replacement
if err := db.addLink(replacement.DerivedFrom, replacement.Derived, invalidated); err != nil {
return fmt.Errorf("failed to add %s as replacement at %s: %w", replacement.Derived, replacement.DerivedFrom, err)
}
return nil
}
// RewindAndInvalidate rolls back the database to just before the invalidated block,
// and then marks the block as invalidated, so that no new data can be added to the DB
// until a Rewind or ReplaceInvalidatedBlock.
func (db *DB) RewindAndInvalidate(invalidated types.DerivedBlockRefPair) error {
db.rwLock.Lock()
defer db.rwLock.Unlock()
i, link, err := db.lookup(invalidated.DerivedFrom.Number, invalidated.Derived.Number)
if err != nil {
return err
}
if link.derivedFrom.Hash != invalidated.DerivedFrom.Hash {
return fmt.Errorf("found derived-from %s, but expected %s: %w",
link.derivedFrom, invalidated.DerivedFrom, types.ErrConflict)
}
if link.derived.Hash != invalidated.Derived.Hash {
return fmt.Errorf("found derived %s, but expected %s: %w",
link.derived, invalidated.Derived, types.ErrConflict)
}
if err := db.store.Truncate(i - 1); err != nil {
return fmt.Errorf("failed to rewind upon block invalidation of %s: %w", invalidated, err)
}
db.m.RecordDBDerivedEntryCount(int64(i))
if err := db.addLink(invalidated.DerivedFrom, invalidated.Derived, invalidated.Derived.Hash); err != nil {
return fmt.Errorf("failed to add invalidation entry %s: %w", invalidated, err)
}
return nil
}
// addLink adds a L1/L2 derivation link, with strong consistency checks.
// if the link invalidates a prior L2 block, that was valid in a prior L1,
// the invalidated hash needs to match it, even if a new derived block replaces it.
func (db *DB) addLink(derivedFrom eth.BlockRef, derived eth.BlockRef, invalidated common.Hash) error {
link := LinkEntry{ link := LinkEntry{
derivedFrom: types.BlockSeal{ derivedFrom: types.BlockSeal{
Hash: derivedFrom.Hash, Hash: derivedFrom.Hash,
...@@ -24,6 +103,12 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { ...@@ -24,6 +103,12 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
Number: derived.Number, Number: derived.Number,
Timestamp: derived.Time, Timestamp: derived.Time,
}, },
invalidated: (invalidated != common.Hash{}) && derived.Hash == invalidated,
}
// If we don't have any entries yet, allow any block to start things off
if db.store.Size() == 0 {
if link.invalidated {
return fmt.Errorf("first DB entry %s cannot be an invalidated entry: %w", link, types.ErrConflict)
} }
e := link.encode() e := link.encode()
if err := db.store.Append(e); err != nil { if err := db.store.Append(e); err != nil {
...@@ -33,10 +118,15 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { ...@@ -33,10 +118,15 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
return nil return nil
} }
lastDerivedFrom, lastDerived, err := db.latest() last, err := db.latest()
if err != nil { if err != nil {
return err return err
} }
if last.invalidated {
return fmt.Errorf("cannot build %s on top of invalidated entry %s: %w", link, last, types.ErrConflict)
}
lastDerivedFrom := last.derivedFrom
lastDerived := last.derived
if lastDerived.ID() == derived.ID() && lastDerivedFrom.ID() == derivedFrom.ID() { if lastDerived.ID() == derived.ID() && lastDerivedFrom.ID() == derivedFrom.ID() {
// it shouldn't be possible, but the ID component of a block ref doesn't include the timestamp // it shouldn't be possible, but the ID component of a block ref doesn't include the timestamp
...@@ -57,10 +147,16 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { ...@@ -57,10 +147,16 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
if lastDerived.Number == derived.Number { if lastDerived.Number == derived.Number {
// Same block height? Then it must be the same block. // Same block height? Then it must be the same block.
// I.e. we encountered an empty L1 block, and the same L2 block continues to be the last block that was derived from it. // I.e. we encountered an empty L1 block, and the same L2 block continues to be the last block that was derived from it.
if invalidated != (common.Hash{}) {
if lastDerived.Hash != invalidated {
return fmt.Errorf("inserting block %s that invalidates %s at height %d, but expected %s", derived.Hash, invalidated, lastDerived.Number, lastDerived.Hash)
}
} else {
if lastDerived.Hash != derived.Hash { if lastDerived.Hash != derived.Hash {
return fmt.Errorf("derived block %s conflicts with known derived block %s at same height: %w", return fmt.Errorf("derived block %s conflicts with known derived block %s at same height: %w",
derived, lastDerived, types.ErrConflict) derived, lastDerived, types.ErrConflict)
} }
}
} else if lastDerived.Number+1 == derived.Number { } else if lastDerived.Number+1 == derived.Number {
if lastDerived.Hash != derived.ParentHash { if lastDerived.Hash != derived.ParentHash {
return fmt.Errorf("derived block %s (parent %s) does not build on %s: %w", return fmt.Errorf("derived block %s (parent %s) does not build on %s: %w",
...@@ -101,18 +197,6 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error { ...@@ -101,18 +197,6 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
derived, derivedFrom, lastDerivedFrom, types.ErrOutOfOrder) derived, derivedFrom, lastDerivedFrom, types.ErrOutOfOrder)
} }
link := LinkEntry{
derivedFrom: types.BlockSeal{
Hash: derivedFrom.Hash,
Number: derivedFrom.Number,
Timestamp: derivedFrom.Time,
},
derived: types.BlockSeal{
Hash: derived.Hash,
Number: derived.Number,
Timestamp: derived.Time,
},
}
e := link.encode() e := link.encode()
if err := db.store.Append(e); err != nil { if err := db.store.Append(e); err != nil {
return err return err
......
...@@ -31,10 +31,10 @@ func TestBadUpdates(t *testing.T) { ...@@ -31,10 +31,10 @@ func TestBadUpdates(t *testing.T) {
fDerived := mockL2(206) fDerived := mockL2(206)
noChange := assertFn(func(t *testing.T, db *DB, m *stubMetrics) { noChange := assertFn(func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dDerivedFrom, derivedFrom) require.Equal(t, dDerivedFrom, pair.DerivedFrom)
require.Equal(t, dDerived, derived) require.Equal(t, dDerived, pair.Derived)
}) })
testCases := []testCase{ testCases := []testCase{
...@@ -69,10 +69,10 @@ func TestBadUpdates(t *testing.T) { ...@@ -69,10 +69,10 @@ func TestBadUpdates(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(dDerivedFrom, common.Hash{0x42}), toRef(eDerived, dDerived.Hash)), types.ErrConflict) require.NoError(t, db.AddDerived(toRef(dDerivedFrom, common.Hash{0x42}), toRef(eDerived, dDerived.Hash)), types.ErrConflict)
}, },
assertFn: func(t *testing.T, db *DB, m *stubMetrics) { assertFn: func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dDerivedFrom, derivedFrom) require.Equal(t, dDerivedFrom, pair.DerivedFrom)
require.Equal(t, eDerived, derived) require.Equal(t, eDerived, pair.Derived)
}, },
}, },
{ {
...@@ -120,10 +120,10 @@ func TestBadUpdates(t *testing.T) { ...@@ -120,10 +120,10 @@ func TestBadUpdates(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(eDerivedFrom, dDerivedFrom.Hash), toRef(dDerived, common.Hash{0x42})), types.ErrConflict) require.NoError(t, db.AddDerived(toRef(eDerivedFrom, dDerivedFrom.Hash), toRef(dDerived, common.Hash{0x42})), types.ErrConflict)
}, },
assertFn: func(t *testing.T, db *DB, m *stubMetrics) { assertFn: func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest() pair, err := db.Latest()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, eDerivedFrom, derivedFrom) require.Equal(t, eDerivedFrom, pair.DerivedFrom)
require.Equal(t, dDerived, derived) require.Equal(t, dDerived, pair.Derived)
}, },
}, },
{ {
......
...@@ -248,18 +248,22 @@ func (db *DB) OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, exe ...@@ -248,18 +248,22 @@ func (db *DB) OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, exe
return return
} }
// LatestSealedBlockNum returns the block number of the block that was last sealed, // LatestSealedBlock returns the block ID of the block that was last sealed,
// or ok=false if there is no sealed block (i.e. empty DB) // or ok=false if there is no sealed block (i.e. empty DB)
func (db *DB) LatestSealedBlockNum() (n uint64, ok bool) { func (db *DB) LatestSealedBlock() (id eth.BlockID, ok bool) {
db.rwLock.RLock() db.rwLock.RLock()
defer db.rwLock.RUnlock() defer db.rwLock.RUnlock()
if db.lastEntryContext.nextEntryIndex == 0 { if db.lastEntryContext.nextEntryIndex == 0 {
return 0, false // empty DB, time to add the first seal return eth.BlockID{}, false // empty DB, time to add the first seal
} }
if !db.lastEntryContext.hasCompleteBlock() { if !db.lastEntryContext.hasCompleteBlock() {
db.log.Debug("New block is already in progress", "num", db.lastEntryContext.blockNum) db.log.Debug("New block is already in progress", "num", db.lastEntryContext.blockNum)
// TODO: is the hash invalid here. When we have a read-lock, can this ever happen?
} }
return db.lastEntryContext.blockNum, true return eth.BlockID{
Hash: db.lastEntryContext.blockHash,
Number: db.lastEntryContext.blockNum,
}, true
} }
// Get returns the hash of the log at the specified blockNum (of the sealed block) // Get returns the hash of the log at the specified blockNum (of the sealed block)
...@@ -549,20 +553,25 @@ func (db *DB) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32 ...@@ -549,20 +553,25 @@ func (db *DB) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32
} }
// Rewind the database to remove any blocks after headBlockNum // Rewind the database to remove any blocks after headBlockNum
// The block at headBlockNum itself is not removed. // The block at newHead.Number itself is not removed.
func (db *DB) Rewind(newHeadBlockNum uint64) error { func (db *DB) Rewind(newHead eth.BlockID) error {
db.rwLock.Lock() db.rwLock.Lock()
defer db.rwLock.Unlock() defer db.rwLock.Unlock()
// Even if the last fully-processed block matches headBlockNum, // Even if the last fully-processed block matches headBlockNum,
// we might still have trailing log events to get rid of. // we might still have trailing log events to get rid of.
iter, err := db.newIteratorAt(newHeadBlockNum, 0) iter, err := db.newIteratorAt(newHead.Number, 0)
if err != nil { if err != nil {
return err return err
} }
if hash, num, ok := iter.SealedBlock(); !ok {
return fmt.Errorf("expected sealed block for rewind reference-point: %w", types.ErrDataCorruption)
} else if hash != newHead.Hash {
return fmt.Errorf("cannot rewind to %s, have %s: %w", newHead, eth.BlockID{Hash: hash, Number: num}, types.ErrConflict)
}
// Truncate to contain idx+1 entries, since indices are 0 based, // Truncate to contain idx+1 entries, since indices are 0 based,
// this deletes everything after idx // this deletes everything after idx
if err := db.store.Truncate(iter.NextIndex()); err != nil { if err := db.store.Truncate(iter.NextIndex()); err != nil {
return fmt.Errorf("failed to truncate to block %v: %w", newHeadBlockNum, err) return fmt.Errorf("failed to truncate to block %s: %w", newHead, err)
} }
// Use db.init() to find the log context for the new latest log entry // Use db.init() to find the log context for the new latest log entry
if err := db.init(true); err != nil { if err := db.init(true); err != nil {
......
...@@ -19,6 +19,13 @@ import ( ...@@ -19,6 +19,13 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
) )
func createID(i int) eth.BlockID {
return eth.BlockID{
Hash: createHash(i),
Number: uint64(i),
}
}
func createHash(i int) common.Hash { func createHash(i int) common.Hash {
if i == -1 { // parent-hash of genesis is zero if i == -1 { // parent-hash of genesis is zero
return common.Hash{} return common.Hash{}
...@@ -85,9 +92,9 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -85,9 +92,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
runDBTest(t, runDBTest(t,
func(t *testing.T, db *DB, m *stubMetrics) {}, func(t *testing.T, db *DB, m *stubMetrics) {},
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum() head, ok := db.LatestSealedBlock()
require.False(t, ok, "empty db expected") require.False(t, ok, "empty db expected")
require.Zero(t, n) require.Equal(t, eth.BlockID{}, head)
idx, err := db.searchCheckpoint(0, 0) idx, err := db.searchCheckpoint(0, 0)
require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db") require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db")
require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db") require.ErrorIs(t, err, types.ErrFuture, "no checkpoint in empty db")
...@@ -101,9 +108,9 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -101,9 +108,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis") require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum() head, ok := db.LatestSealedBlock()
require.True(t, ok, "genesis block expected") require.True(t, ok, "genesis block expected")
require.Equal(t, genesis.Number, n) require.Equal(t, genesis, head)
idx, err := db.searchCheckpoint(0, 0) idx, err := db.searchCheckpoint(0, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, idx, "genesis block as checkpoint 0") require.Zero(t, idx, "genesis block as checkpoint 0")
...@@ -124,9 +131,9 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -124,9 +131,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis") require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum() head, ok := db.LatestSealedBlock()
require.True(t, ok, "genesis block expected") require.True(t, ok, "genesis block expected")
require.Equal(t, genesis.Number, n) require.Equal(t, genesis, head)
idx, err := db.searchCheckpoint(genesis.Number, 0) idx, err := db.searchCheckpoint(genesis.Number, 0)
require.NoError(t, err) require.NoError(t, err)
require.Zero(t, idx, "anchor block as checkpoint 0") require.Zero(t, idx, "anchor block as checkpoint 0")
...@@ -152,9 +159,9 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -152,9 +159,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(genesis.Hash, block1, 5001), "seal block 1") require.NoError(t, db.SealBlock(genesis.Hash, block1, 5001), "seal block 1")
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum() head, ok := db.LatestSealedBlock()
require.True(t, ok, "block 1 expected") require.True(t, ok, "block 1 expected")
require.Equal(t, block1.Number, n) require.Equal(t, block1, head)
idx, err := db.searchCheckpoint(block1.Number, 0) idx, err := db.searchCheckpoint(block1.Number, 0)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, entrydb.EntryIdx(0), idx, "checkpoint 0 still for block 1") require.Equal(t, entrydb.EntryIdx(0), idx, "checkpoint 0 still for block 1")
...@@ -187,10 +194,10 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -187,10 +194,10 @@ func TestLatestSealedBlockNum(t *testing.T) {
} }
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum() head, ok := db.LatestSealedBlock()
require.True(t, ok, "latest block expected") require.True(t, ok, "latest block expected")
expected := uint64(260) expected := uint64(260)
require.Equal(t, expected, n) require.Equal(t, expected, head.Number)
idx, err := db.searchCheckpoint(expected, 0) idx, err := db.searchCheckpoint(expected, 0)
require.NoError(t, err) require.NoError(t, err)
// It costs 2 entries per block, so if we add more than 1 checkpoint worth of blocks, // It costs 2 entries per block, so if we add more than 1 checkpoint worth of blocks,
...@@ -198,12 +205,12 @@ func TestLatestSealedBlockNum(t *testing.T) { ...@@ -198,12 +205,12 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.Equal(t, entrydb.EntryIdx(searchCheckpointFrequency*2), idx, "checkpoint 1 reached") require.Equal(t, entrydb.EntryIdx(searchCheckpointFrequency*2), idx, "checkpoint 1 reached")
// Test if we can open the block // Test if we can open the block
ref, logCount, execMsgs, err := db.OpenBlock(n) ref, logCount, execMsgs, err := db.OpenBlock(head.Number)
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, execMsgs) require.Empty(t, execMsgs)
require.Zero(t, logCount) require.Zero(t, logCount)
require.Equal(t, createHash(int(n)), ref.Hash) require.Equal(t, head.Hash, ref.Hash)
require.Equal(t, uint64(5000)+n, ref.Time) require.Equal(t, uint64(5000)+head.Number, ref.Time)
}) })
}) })
} }
...@@ -1017,11 +1024,11 @@ func TestRewind(t *testing.T) { ...@@ -1017,11 +1024,11 @@ func TestRewind(t *testing.T) {
t.Run("WhenEmpty", func(t *testing.T) { t.Run("WhenEmpty", func(t *testing.T) {
runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {}, runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {},
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
require.ErrorIs(t, db.Rewind(100), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(100)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(100), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(100)), types.ErrFuture)
// Genesis is a block to, not present in an empty DB // Genesis is a block to, not present in an empty DB
require.ErrorIs(t, db.Rewind(0), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(0)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(0), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(0)), types.ErrFuture)
}) })
}) })
...@@ -1039,8 +1046,8 @@ func TestRewind(t *testing.T) { ...@@ -1039,8 +1046,8 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504)) require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
require.NoError(t, db.AddLog(createHash(4), bl52, 0, nil)) require.NoError(t, db.AddLog(createHash(4), bl52, 0, nil))
// cannot rewind to a block that is not sealed yet // cannot rewind to a block that is not sealed yet
require.ErrorIs(t, db.Rewind(53), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(53)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(53), types.ErrFuture) require.ErrorIs(t, db.Rewind(createID(53)), types.ErrFuture)
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
requireContains(t, db, 51, 0, createHash(1)) requireContains(t, db, 51, 0, createHash(1))
...@@ -1059,8 +1066,8 @@ func TestRewind(t *testing.T) { ...@@ -1059,8 +1066,8 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil)) require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil)) require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil))
// cannot go back to an unknown block // cannot go back to an unknown block
require.ErrorIs(t, db.Rewind(25), types.ErrSkipped) require.ErrorIs(t, db.Rewind(createID(25)), types.ErrSkipped)
require.ErrorIs(t, db.Rewind(25), types.ErrSkipped) require.ErrorIs(t, db.Rewind(createID(25)), types.ErrSkipped)
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
// block 51 is not sealed yet // block 51 is not sealed yet
...@@ -1082,7 +1089,7 @@ func TestRewind(t *testing.T) { ...@@ -1082,7 +1089,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil)) require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
bl52 := eth.BlockID{Hash: createHash(52), Number: 52} bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504)) require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
require.NoError(t, db.Rewind(51)) require.NoError(t, db.Rewind(createID(51)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
requireContains(t, db, 51, 0, createHash(1)) requireContains(t, db, 51, 0, createHash(1))
...@@ -1111,7 +1118,7 @@ func TestRewind(t *testing.T) { ...@@ -1111,7 +1118,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil)) require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
bl52 := eth.BlockID{Hash: createHash(52), Number: 52} bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504)) require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
require.NoError(t, db.Rewind(51)) require.NoError(t, db.Rewind(createID(51)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
require.EqualValues(t, searchCheckpointFrequency+2+2, m.entryCount, "Should have deleted second checkpoint") require.EqualValues(t, searchCheckpointFrequency+2+2, m.entryCount, "Should have deleted second checkpoint")
...@@ -1134,7 +1141,7 @@ func TestRewind(t *testing.T) { ...@@ -1134,7 +1141,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl, 1, nil)) require.NoError(t, db.AddLog(createHash(2), bl, 1, nil))
} }
} }
require.NoError(t, db.Rewind(15)) require.NoError(t, db.Rewind(createID(15)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
requireContains(t, db, 15, 0, createHash(1)) requireContains(t, db, 15, 0, createHash(1))
...@@ -1157,7 +1164,7 @@ func TestRewind(t *testing.T) { ...@@ -1157,7 +1164,7 @@ func TestRewind(t *testing.T) {
} }
} }
// We ended at 30, and sealed it, nothing left to prune // We ended at 30, and sealed it, nothing left to prune
require.NoError(t, db.Rewind(30)) require.NoError(t, db.Rewind(createID(30)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
requireContains(t, db, 20, 0, createHash(1)) requireContains(t, db, 20, 0, createHash(1))
...@@ -1180,7 +1187,7 @@ func TestRewind(t *testing.T) { ...@@ -1180,7 +1187,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl, 1, nil)) require.NoError(t, db.AddLog(createHash(2), bl, 1, nil))
} }
} }
require.NoError(t, db.Rewind(16)) require.NoError(t, db.Rewind(createID(16)))
}, },
func(t *testing.T, db *DB, m *stubMetrics) { func(t *testing.T, db *DB, m *stubMetrics) {
bl29 := eth.BlockID{Hash: createHash(29), Number: 29} bl29 := eth.BlockID{Hash: createHash(29), Number: 29}
......
...@@ -27,33 +27,35 @@ func (db *ChainsDB) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) { ...@@ -27,33 +27,35 @@ func (db *ChainsDB) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) {
if !knownChain { if !knownChain {
return 0, false return 0, false
} }
return logDB.LatestSealedBlockNum() bl, ok := logDB.LatestSealedBlock()
return bl.Number, ok
} }
// LastCommonL1 returns the latest common L1 block between all chains in the database. // LastCommonL1 returns the latest common L1 block between all chains in the database.
// it only considers block numbers, not hash. That's because the L1 source is the same for all chains // it only considers block numbers, not hash. That's because the L1 source is the same for all chains
// this data can be used to determine the starting point for L1 processing // this data can be used to determine the starting point for L1 processing
func (db *ChainsDB) LastCommonL1() (types.BlockSeal, error) { func (db *ChainsDB) LastCommonL1() (types.BlockSeal, error) {
common := types.BlockSeal{} commonL1 := types.BlockSeal{}
for _, chain := range db.depSet.Chains() { for _, chain := range db.depSet.Chains() {
ldb, ok := db.localDBs.Get(chain) ldb, ok := db.localDBs.Get(chain)
if !ok { if !ok {
return types.BlockSeal{}, types.ErrUnknownChain return types.BlockSeal{}, types.ErrUnknownChain
} }
_, derivedFrom, err := ldb.Latest() last, err := ldb.Latest()
if err != nil { if err != nil {
return types.BlockSeal{}, fmt.Errorf("failed to determine Last Common L1: %w", err) return types.BlockSeal{}, fmt.Errorf("failed to determine Last Common L1: %w", err)
} }
common = derivedFrom derivedFrom := last.DerivedFrom
commonL1 = derivedFrom
// if the common block isn't yet set, // if the common block isn't yet set,
// or if the new common block is older than the current common block // or if the new common block is older than the current common block
// set the common block // set the common block
if common == (types.BlockSeal{}) || if commonL1 == (types.BlockSeal{}) ||
derivedFrom.Number < common.Number { derivedFrom.Number < commonL1.Number {
common = derivedFrom commonL1 = derivedFrom
} }
} }
return common, nil return commonL1, nil
} }
func (db *ChainsDB) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error { func (db *ChainsDB) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error {
...@@ -120,11 +122,11 @@ func (db *ChainsDB) LocalUnsafe(chainID eth.ChainID) (types.BlockSeal, error) { ...@@ -120,11 +122,11 @@ func (db *ChainsDB) LocalUnsafe(chainID eth.ChainID) (types.BlockSeal, error) {
if !ok { if !ok {
return types.BlockSeal{}, types.ErrUnknownChain return types.BlockSeal{}, types.ErrUnknownChain
} }
n, ok := eventsDB.LatestSealedBlockNum() head, ok := eventsDB.LatestSealedBlock()
if !ok { if !ok {
return types.BlockSeal{}, types.ErrFuture return types.BlockSeal{}, types.ErrFuture
} }
return eventsDB.FindSealedBlock(n) return eventsDB.FindSealedBlock(head.Number)
} }
func (db *ChainsDB) CrossUnsafe(chainID eth.ChainID) (types.BlockSeal, error) { func (db *ChainsDB) CrossUnsafe(chainID eth.ChainID) (types.BlockSeal, error) {
...@@ -149,8 +151,7 @@ func (db *ChainsDB) LocalSafe(chainID eth.ChainID) (pair types.DerivedBlockSealP ...@@ -149,8 +151,7 @@ func (db *ChainsDB) LocalSafe(chainID eth.ChainID) (pair types.DerivedBlockSealP
if !ok { if !ok {
return types.DerivedBlockSealPair{}, types.ErrUnknownChain return types.DerivedBlockSealPair{}, types.ErrUnknownChain
} }
df, d, err := localDB.Latest() return localDB.Latest()
return types.DerivedBlockSealPair{DerivedFrom: df, Derived: d}, err
} }
func (db *ChainsDB) CrossSafe(chainID eth.ChainID) (pair types.DerivedBlockSealPair, err error) { func (db *ChainsDB) CrossSafe(chainID eth.ChainID) (pair types.DerivedBlockSealPair, err error) {
...@@ -158,8 +159,7 @@ func (db *ChainsDB) CrossSafe(chainID eth.ChainID) (pair types.DerivedBlockSealP ...@@ -158,8 +159,7 @@ func (db *ChainsDB) CrossSafe(chainID eth.ChainID) (pair types.DerivedBlockSealP
if !ok { if !ok {
return types.DerivedBlockSealPair{}, types.ErrUnknownChain return types.DerivedBlockSealPair{}, types.ErrUnknownChain
} }
df, d, err := crossDB.Latest() return crossDB.Latest()
return types.DerivedBlockSealPair{DerivedFrom: df, Derived: d}, err
} }
func (db *ChainsDB) FinalizedL1() eth.BlockRef { func (db *ChainsDB) FinalizedL1() eth.BlockRef {
...@@ -177,19 +177,19 @@ func (db *ChainsDB) Finalized(chainID eth.ChainID) (types.BlockSeal, error) { ...@@ -177,19 +177,19 @@ func (db *ChainsDB) Finalized(chainID eth.ChainID) (types.BlockSeal, error) {
if !ok { if !ok {
return types.BlockSeal{}, types.ErrUnknownChain return types.BlockSeal{}, types.ErrUnknownChain
} }
latestDerivedFrom, latestDerived, err := xDB.Latest() latest, err := xDB.Latest()
if err != nil { if err != nil {
return types.BlockSeal{}, fmt.Errorf("could not get the latest derived pair for chain %s: %w", chainID, err) return types.BlockSeal{}, fmt.Errorf("could not get the latest derived pair for chain %s: %w", chainID, err)
} }
// if the finalized L1 block is newer than the latest L1 block used to derive L2 blocks, // if the finalized L1 block is newer than the latest L1 block used to derive L2 blocks,
// the finality signal automatically applies to all previous blocks, including the latest derived block // the finality signal automatically applies to all previous blocks, including the latest derived block
if finalizedL1.Number > latestDerivedFrom.Number { if finalizedL1.Number > latest.DerivedFrom.Number {
db.logger.Warn("Finalized L1 block is newer than the latest L1 for this chain. Assuming latest L2 is finalized", db.logger.Warn("Finalized L1 block is newer than the latest L1 for this chain. Assuming latest L2 is finalized",
"chain", chainID, "chain", chainID,
"finalizedL1", finalizedL1.Number, "finalizedL1", finalizedL1.Number,
"latestDerivedFrom", latestDerivedFrom.Number, "latestDerivedFrom", latest.DerivedFrom.Number,
"latestDerived", latestDerivedFrom) "latestDerived", latest.DerivedFrom)
return latestDerived, nil return latest.Derived, nil
} }
// otherwise, use the finalized L1 block to determine the final L2 block that was derived from it // otherwise, use the finalized L1 block to determine the final L2 block that was derived from it
...@@ -290,7 +290,7 @@ func (db *ChainsDB) CrossDerivedFrom(chain eth.ChainID, derived eth.BlockID) (de ...@@ -290,7 +290,7 @@ func (db *ChainsDB) CrossDerivedFrom(chain eth.ChainID, derived eth.BlockID) (de
// //
// Or ErrOutOfScope, with non-zero derivedFromScope, // Or ErrOutOfScope, with non-zero derivedFromScope,
// if additional L1 data is needed to cross-verify the candidate L2 block. // if additional L1 data is needed to cross-verify the candidate L2 block.
func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, crossSafe eth.BlockRef, err error) { func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, candidate eth.BlockRef, err error) {
xDB, ok := db.crossDBs.Get(chain) xDB, ok := db.crossDBs.Get(chain)
if !ok { if !ok {
return eth.BlockRef{}, eth.BlockRef{}, types.ErrUnknownChain return eth.BlockRef{}, eth.BlockRef{}, types.ErrUnknownChain
...@@ -312,23 +312,23 @@ func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, cro ...@@ -312,23 +312,23 @@ func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, cro
// (D, 2) -> 2 is in scope, stay on D, promote candidate to cross-safe // (D, 2) -> 2 is in scope, stay on D, promote candidate to cross-safe
// (D, 3) -> look at 3 next, see if we have to bump L1 yet, try with same L1 scope first // (D, 3) -> look at 3 next, see if we have to bump L1 yet, try with same L1 scope first
crossDerivedFrom, crossDerived, err := xDB.Latest() crossSafe, err := xDB.Latest()
if err != nil { if err != nil {
if errors.Is(err, types.ErrFuture) { if errors.Is(err, types.ErrFuture) {
// If we do not have any cross-safe block yet, then return the first local-safe block. // If we do not have any cross-safe block yet, then return the first local-safe block.
derivedFrom, derived, err := lDB.First() first, err := lDB.First()
if err != nil { if err != nil {
return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find first local-safe block: %w", err) return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find first local-safe block: %w", err)
} }
// the first derivedFrom (L1 block) is unlikely to be the genesis block, // the first derivedFrom (L1 block) is unlikely to be the genesis block,
derivedFromRef, err := derivedFrom.WithParent(eth.BlockID{}) derivedFromRef, err := first.DerivedFrom.WithParent(eth.BlockID{})
if err != nil { if err != nil {
// if the first derivedFrom isn't the genesis block, just warn and continue anyway // if the first derivedFrom isn't the genesis block, just warn and continue anyway
db.logger.Warn("First DerivedFrom is not genesis block") db.logger.Warn("First DerivedFrom is not genesis block")
derivedFromRef = derivedFrom.ForceWithParent(eth.BlockID{}) derivedFromRef = first.DerivedFrom.ForceWithParent(eth.BlockID{})
} }
// the first derived must be the genesis block, panic otherwise // the first derived must be the genesis block, panic otherwise
derivedRef := derived.MustWithParent(eth.BlockID{}) derivedRef := first.Derived.MustWithParent(eth.BlockID{})
return derivedFromRef, derivedRef, nil return derivedFromRef, derivedRef, nil
} }
return eth.BlockRef{}, eth.BlockRef{}, err return eth.BlockRef{}, eth.BlockRef{}, err
...@@ -340,42 +340,42 @@ func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, cro ...@@ -340,42 +340,42 @@ func (db *ChainsDB) CandidateCrossSafe(chain eth.ChainID) (derivedFromScope, cro
// While the local-safe block isn't cross-safe given limited L1 scope, we'll keep bumping the L1 scope, // While the local-safe block isn't cross-safe given limited L1 scope, we'll keep bumping the L1 scope,
// And update cross-safe accordingly. // And update cross-safe accordingly.
// This method will keep returning the latest known scope that has been verified to be cross-safe. // This method will keep returning the latest known scope that has been verified to be cross-safe.
candidateFrom, candidate, err := lDB.NextDerived(crossDerived.ID()) candidatePair, err := lDB.NextDerived(crossSafe.Derived.ID())
if err != nil { if err != nil {
return eth.BlockRef{}, eth.BlockRef{}, err return eth.BlockRef{}, eth.BlockRef{}, err
} }
candidateRef := candidate.MustWithParent(crossDerived.ID()) candidateRef := candidatePair.Derived.MustWithParent(crossSafe.Derived.ID())
parentDerivedFrom, err := lDB.PreviousDerivedFrom(candidateFrom.ID()) parentDerivedFrom, err := lDB.PreviousDerivedFrom(candidatePair.DerivedFrom.ID())
// if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst // if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst
// in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown // in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown
if errors.Is(err, types.ErrPreviousToFirst) { if errors.Is(err, types.ErrPreviousToFirst) {
parentDerivedFrom = types.BlockSeal{} parentDerivedFrom = types.BlockSeal{}
} else if err != nil { } else if err != nil {
return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find parent-block of derived-from %s: %w", candidateFrom, err) return eth.BlockRef{}, eth.BlockRef{}, fmt.Errorf("failed to find parent-block of derived-from %s: %w", candidatePair.DerivedFrom, err)
} }
candidateFromRef := candidateFrom.MustWithParent(parentDerivedFrom.ID()) candidateFromRef := candidatePair.DerivedFrom.MustWithParent(parentDerivedFrom.ID())
// Allow increment of DA by 1, if we know the floor (due to local safety) is 1 ahead of the current cross-safe L1 scope. // Allow increment of DA by 1, if we know the floor (due to local safety) is 1 ahead of the current cross-safe L1 scope.
if candidateFrom.Number > crossDerivedFrom.Number+1 { if candidatePair.DerivedFrom.Number > crossSafe.DerivedFrom.Number+1 {
// If we are not ready to process the candidate block, // If we are not ready to process the candidate block,
// then we need to stick to the current scope, so the caller can bump up from there. // then we need to stick to the current scope, so the caller can bump up from there.
var crossDerivedFromRef eth.BlockRef var crossDerivedFromRef eth.BlockRef
parent, err := lDB.PreviousDerivedFrom(crossDerivedFrom.ID()) parent, err := lDB.PreviousDerivedFrom(crossSafe.DerivedFrom.ID())
// if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst // if we are working with the first item in the database, PreviousDerivedFrom will return ErrPreviousToFirst
// in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown // in which case we can attach a zero parent to the cross-derived-from block, as the parent block is unknown
if errors.Is(err, types.ErrPreviousToFirst) { if errors.Is(err, types.ErrPreviousToFirst) {
crossDerivedFromRef = crossDerivedFrom.ForceWithParent(eth.BlockID{}) crossDerivedFromRef = crossSafe.DerivedFrom.ForceWithParent(eth.BlockID{})
} else if err != nil { } else if err != nil {
return eth.BlockRef{}, eth.BlockRef{}, return eth.BlockRef{}, eth.BlockRef{},
fmt.Errorf("failed to find parent-block of cross-derived-from %s: %w", crossDerivedFrom, err) fmt.Errorf("failed to find parent-block of cross-derived-from %s: %w", crossSafe.DerivedFrom, err)
} else { } else {
crossDerivedFromRef = crossDerivedFrom.MustWithParent(parent.ID()) crossDerivedFromRef = crossSafe.DerivedFrom.MustWithParent(parent.ID())
} }
return crossDerivedFromRef, eth.BlockRef{}, return crossDerivedFromRef, eth.BlockRef{},
fmt.Errorf("candidate is from %s, while current scope is %s: %w", fmt.Errorf("candidate is from %s, while current scope is %s: %w",
candidateFrom, crossDerivedFrom, types.ErrOutOfScope) candidateFromRef, crossSafe.DerivedFrom, types.ErrOutOfScope)
} }
return candidateFromRef, candidateRef, nil return candidateFromRef, candidateRef, nil
} }
......
...@@ -40,12 +40,12 @@ func (db *ChainsDB) SealBlock(chain eth.ChainID, block eth.BlockRef) error { ...@@ -40,12 +40,12 @@ func (db *ChainsDB) SealBlock(chain eth.ChainID, block eth.BlockRef) error {
return nil return nil
} }
func (db *ChainsDB) Rewind(chain eth.ChainID, headBlockNum uint64) error { func (db *ChainsDB) Rewind(chain eth.ChainID, headBlock eth.BlockID) error {
logDB, ok := db.logDBs.Get(chain) logDB, ok := db.logDBs.Get(chain)
if !ok { if !ok {
return fmt.Errorf("cannot Rewind: %w: %s", types.ErrUnknownChain, chain) return fmt.Errorf("cannot Rewind: %w: %s", types.ErrUnknownChain, chain)
} }
return logDB.Rewind(headBlockNum) return logDB.Rewind(headBlock)
} }
func (db *ChainsDB) UpdateLocalSafe(chain eth.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) { func (db *ChainsDB) UpdateLocalSafe(chain eth.ChainID, derivedFrom eth.BlockRef, lastDerived eth.BlockRef) {
......
...@@ -29,7 +29,7 @@ type LogProcessor interface { ...@@ -29,7 +29,7 @@ type LogProcessor interface {
} }
type DatabaseRewinder interface { type DatabaseRewinder interface {
Rewind(chain eth.ChainID, headBlockNum uint64) error Rewind(chain eth.ChainID, headBlock eth.BlockID) error
LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool)
} }
...@@ -247,7 +247,7 @@ func (s *ChainProcessor) process(ctx context.Context, next eth.BlockRef, receipt ...@@ -247,7 +247,7 @@ func (s *ChainProcessor) process(ctx context.Context, next eth.BlockRef, receipt
} }
// Try to rewind the database to the previous block to remove any logs from this block that were written // Try to rewind the database to the previous block to remove any logs from this block that were written
if err := s.rewinder.Rewind(s.chain, next.Number-1); err != nil { if err := s.rewinder.Rewind(s.chain, next.ParentID()); err != nil {
// If any logs were written, our next attempt to write will fail and we'll retry this rewind. // If any logs were written, our next attempt to write will fail and we'll retry this rewind.
// If no logs were written successfully then the rewind wouldn't have done anything anyway. // If no logs were written successfully then the rewind wouldn't have done anything anyway.
s.log.Error("Failed to rewind after error processing block", "block", next, "err", err) s.log.Error("Failed to rewind after error processing block", "block", next, "err", err)
......
...@@ -15,6 +15,8 @@ var ( ...@@ -15,6 +15,8 @@ var (
ErrFuture = errors.New("future data") ErrFuture = errors.New("future data")
// ErrConflict happens when we know for sure that there is different canonical data // ErrConflict happens when we know for sure that there is different canonical data
ErrConflict = errors.New("conflicting data") ErrConflict = errors.New("conflicting data")
// ErrAwaitReplacementBlock happens when we know for sure that a replacement block is needed before progress can be made.
ErrAwaitReplacementBlock = errors.New("awaiting replacement block")
// ErrStop can be used in iterators to indicate iteration has to stop // ErrStop can be used in iterators to indicate iteration has to stop
ErrStop = errors.New("iter stop") ErrStop = errors.New("iter stop")
// ErrOutOfScope is when data is accessed, but access is not allowed, because of a limited scope. // ErrOutOfScope is when data is accessed, but access is not allowed, because of a limited scope.
......
...@@ -263,8 +263,8 @@ func LogToMessagePayload(l *ethTypes.Log) []byte { ...@@ -263,8 +263,8 @@ func LogToMessagePayload(l *ethTypes.Log) []byte {
// DerivedBlockRefPair is a pair of block refs, where Derived (L2) is derived from DerivedFrom (L1). // DerivedBlockRefPair is a pair of block refs, where Derived (L2) is derived from DerivedFrom (L1).
type DerivedBlockRefPair struct { type DerivedBlockRefPair struct {
DerivedFrom eth.BlockRef DerivedFrom eth.BlockRef `json:"derivedFrom"`
Derived eth.BlockRef Derived eth.BlockRef `json:"derived"`
} }
func (refs *DerivedBlockRefPair) IDs() DerivedIDPair { func (refs *DerivedBlockRefPair) IDs() DerivedIDPair {
...@@ -276,8 +276,8 @@ func (refs *DerivedBlockRefPair) IDs() DerivedIDPair { ...@@ -276,8 +276,8 @@ func (refs *DerivedBlockRefPair) IDs() DerivedIDPair {
// DerivedBlockSealPair is a pair of block seals, where Derived (L2) is derived from DerivedFrom (L1). // DerivedBlockSealPair is a pair of block seals, where Derived (L2) is derived from DerivedFrom (L1).
type DerivedBlockSealPair struct { type DerivedBlockSealPair struct {
DerivedFrom BlockSeal DerivedFrom BlockSeal `json:"derivedFrom"`
Derived BlockSeal Derived BlockSeal `json:"derived"`
} }
func (seals *DerivedBlockSealPair) IDs() DerivedIDPair { func (seals *DerivedBlockSealPair) IDs() DerivedIDPair {
...@@ -289,8 +289,8 @@ func (seals *DerivedBlockSealPair) IDs() DerivedIDPair { ...@@ -289,8 +289,8 @@ func (seals *DerivedBlockSealPair) IDs() DerivedIDPair {
// DerivedIDPair is a pair of block IDs, where Derived (L2) is derived from DerivedFrom (L1). // DerivedIDPair is a pair of block IDs, where Derived (L2) is derived from DerivedFrom (L1).
type DerivedIDPair struct { type DerivedIDPair struct {
DerivedFrom eth.BlockID DerivedFrom eth.BlockID `json:"derivedFrom"`
Derived eth.BlockID Derived eth.BlockID `json:"derived"`
} }
// ManagedEvent is an event sent by the managed node to the supervisor, // ManagedEvent is an event sent by the managed node to the supervisor,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment