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 {
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,
// returning the block seal if it was found.
......@@ -51,14 +51,14 @@ type LogStorage interface {
}
type LocalDerivedFromStorage interface {
First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error)
Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error)
First() (pair types.DerivedBlockSealPair, err error)
Latest() (pair types.DerivedBlockSealPair, err error)
AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error
LastDerivedAt(derivedFrom eth.BlockID) (derived 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)
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)
PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error)
}
......@@ -168,15 +168,15 @@ func (db *ChainsDB) AddCrossUnsafeTracker(chainID eth.ChainID) {
func (db *ChainsDB) ResumeFromLastSealedBlock() error {
var result error
db.logDBs.Range(func(chain eth.ChainID, logStore LogStorage) bool {
headNum, ok := logStore.LatestSealedBlockNum()
head, ok := logStore.LatestSealedBlock()
if !ok {
// db must be empty, nothing to rewind to
db.logger.Info("Resuming, but found no DB contents", "chain", chain)
return true
}
db.logger.Info("Resuming, starting from last sealed block", "head", headNum)
if err := logStore.Rewind(headNum); err != nil {
result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, headNum)
db.logger.Info("Resuming, starting from last sealed block", "head", head)
if err := logStore.Rewind(head); err != nil {
result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, head)
return false
}
return true
......
......@@ -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.
func (db *DB) Rewind(derivedFrom uint64) error {
db.rwLock.Lock()
defer db.rwLock.Unlock()
index, _, err := db.lastDerivedAt(derivedFrom)
if err != nil {
return fmt.Errorf("failed to find point to rewind to: %w", err)
......@@ -66,29 +68,36 @@ func (db *DB) Rewind(derivedFrom uint64) error {
}
// 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()
defer db.rwLock.RUnlock()
lastIndex := db.store.LastEntryIdx()
if lastIndex < 0 {
return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture
return types.DerivedBlockSealPair{}, types.ErrFuture
}
last, err := db.readAt(0)
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) {
db.rwLock.RLock()
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)
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)
}
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,
// 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).
// 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()
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.
func (db *DB) latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error) {
func (db *DB) latest() (link LinkEntry, err error) {
lastIndex := db.store.LastEntryIdx()
if lastIndex < 0 {
return types.BlockSeal{}, types.BlockSeal{}, types.ErrFuture
return LinkEntry{}, types.ErrFuture
}
last, err := db.readAt(lastIndex)
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.
// 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) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
......@@ -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",
derivedFrom, link.derivedFrom, types.ErrConflict)
}
if link.invalidated {
return types.BlockSeal{}, types.ErrAwaitReplacementBlock
}
return link.derived, nil
}
// 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) {
// NextDerived finds the next L2 block after derived, and what it was derived from.
// 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()
defer db.rwLock.RUnlock()
// get the last time this L2 block was seen.
selfIndex, self, err := db.lastDerivedFrom(derived.Number)
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 {
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)
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.
......@@ -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) {
db.rwLock.RLock()
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.
selfIndex, self, err := db.firstDerivedAt(derivedFrom.Number)
if err != nil {
......@@ -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.
// 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) {
// Either one or both of the two entries will be an increment by 1.
// 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()
defer db.rwLock.RUnlock()
selfIndex, selfLink, err := db.lookup(derivedFrom.Number, derived.Number)
if err != nil {
return types.BlockSeal{}, types.BlockSeal{}, err
return types.DerivedBlockSealPair{}, err
}
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 {
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)
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) {
......
......@@ -72,10 +72,10 @@ func TestEmptyDB(t *testing.T) {
runDBTest(t,
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)
_, _, err = db.First()
_, err = db.First()
require.ErrorIs(t, err, types.ErrFuture)
_, err = db.LastDerivedAt(eth.BlockID{})
......@@ -87,7 +87,7 @@ func TestEmptyDB(t *testing.T) {
_, err = db.PreviousDerived(eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture)
_, _, err = db.NextDerived(eth.BlockID{})
_, err = db.NextDerived(eth.BlockID{})
require.ErrorIs(t, err, types.ErrFuture)
_, err = db.PreviousDerivedFrom(eth.BlockID{})
......@@ -96,7 +96,7 @@ func TestEmptyDB(t *testing.T) {
_, err = db.NextDerivedFrom(eth.BlockID{})
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)
})
}
......@@ -139,23 +139,23 @@ func TestSingleEntryDB(t *testing.T) {
},
func(t *testing.T, db *DB, m *stubMetrics) {
// First
derivedFrom, derived, err := db.First()
pair, err := db.First()
require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom)
require.Equal(t, expectedDerived, derived)
require.Equal(t, expectedDerivedFrom, pair.DerivedFrom)
require.Equal(t, expectedDerived, pair.Derived)
// Latest
derivedFrom, derived, err = db.Latest()
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom)
require.Equal(t, expectedDerived, derived)
require.Equal(t, expectedDerivedFrom, pair.DerivedFrom)
require.Equal(t, expectedDerived, pair.Derived)
// FirstAfter Latest
_, _, err = db.FirstAfter(derivedFrom.ID(), derived.ID())
_, err = db.FirstAfter(pair.DerivedFrom.ID(), pair.Derived.ID())
require.ErrorIs(t, err, types.ErrFuture)
// LastDerivedAt
derived, err = db.LastDerivedAt(expectedDerivedFrom.ID())
derived, err := db.LastDerivedAt(expectedDerivedFrom.ID())
require.NoError(t, err)
require.Equal(t, expectedDerived, derived)
......@@ -164,13 +164,13 @@ func TestSingleEntryDB(t *testing.T) {
require.ErrorIs(t, err, types.ErrConflict)
// 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)
_, _, 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)
// DerivedFrom
derivedFrom, err = db.DerivedFrom(expectedDerived.ID())
derivedFrom, err := db.DerivedFrom(expectedDerived.ID())
require.NoError(t, err)
require.Equal(t, expectedDerivedFrom, derivedFrom)
......@@ -189,7 +189,7 @@ func TestSingleEntryDB(t *testing.T) {
require.Equal(t, types.BlockSeal{}, prev, "zeroed seal before first entry")
// NextDerived
_, _, err = db.NextDerived(expectedDerived.ID())
_, err = db.NextDerived(expectedDerived.ID())
require.ErrorIs(t, err, types.ErrFuture)
// NextDerivedFrom
......@@ -197,7 +197,7 @@ func TestSingleEntryDB(t *testing.T) {
require.ErrorIs(t, err, types.ErrFuture)
// FirstAfter
_, _, err = db.FirstAfter(expectedDerivedFrom.ID(), expectedDerived.ID())
_, err = db.FirstAfter(expectedDerivedFrom.ID(), expectedDerived.ID())
require.ErrorIs(t, err, types.ErrFuture)
})
}
......@@ -212,7 +212,7 @@ func TestGap(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(expectedDerivedFrom, mockL1(0).Hash), toRef(expectedDerived, mockL2(0).Hash)))
},
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)
_, err = db.NextDerivedFrom(mockL1(0).ID())
......@@ -235,24 +235,24 @@ func TestThreeEntryDB(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block2, pair.DerivedFrom)
require.Equal(t, l2Block2, pair.Derived)
derivedFrom, derived, err = db.First()
pair, err = db.First()
require.NoError(t, err)
require.Equal(t, l1Block0, derivedFrom)
require.Equal(t, l2Block0, derived)
require.Equal(t, l1Block0, pair.DerivedFrom)
require.Equal(t, l2Block0, pair.Derived)
derived, err = db.LastDerivedAt(l1Block2.ID())
derived, err := db.LastDerivedAt(l1Block2.ID())
require.NoError(t, err)
require.Equal(t, l2Block2, derived)
_, err = db.LastDerivedAt(eth.BlockID{Hash: common.Hash{0xaa}, Number: l1Block2.Number})
require.ErrorIs(t, err, types.ErrConflict)
derivedFrom, err = db.DerivedFrom(l2Block2.ID())
derivedFrom, err := db.DerivedFrom(l2Block2.ID())
require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom)
......@@ -287,17 +287,17 @@ func TestThreeEntryDB(t *testing.T) {
require.NoError(t, err)
require.Equal(t, l2Block1, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID())
next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err)
require.Equal(t, l2Block1, derived)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block1, next.Derived)
require.Equal(t, l1Block1, next.DerivedFrom)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID())
next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block2, derivedFrom)
require.Equal(t, l2Block2, next.Derived)
require.Equal(t, l1Block2, next.DerivedFrom)
_, _, err = db.NextDerived(l2Block2.ID())
_, err = db.NextDerived(l2Block2.ID())
require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block0.ID())
......@@ -323,18 +323,18 @@ func TestThreeEntryDB(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block2.ID())
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)
derivedFrom, derived, err = db.FirstAfter(l1Block0.ID(), l2Block0.ID())
next, err = db.FirstAfter(l1Block0.ID(), l2Block0.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block1, derived)
require.Equal(t, l1Block1, next.DerivedFrom)
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.Equal(t, l1Block2, derivedFrom)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block2, next.DerivedFrom)
require.Equal(t, l2Block2, next.Derived)
})
}
......@@ -364,17 +364,17 @@ func TestFastL2Batcher(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block2, l1Block1.Hash), toRef(l2Block5, l2Block4.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom)
require.Equal(t, l2Block5, derived)
require.Equal(t, l1Block2, pair.DerivedFrom)
require.Equal(t, l2Block5, pair.Derived)
derived, err = db.LastDerivedAt(l1Block2.ID())
derived, err := db.LastDerivedAt(l1Block2.ID())
require.NoError(t, err)
require.Equal(t, l2Block5, derived)
// test what tip was derived from
derivedFrom, err = db.DerivedFrom(l2Block5.ID())
derivedFrom, err := db.DerivedFrom(l2Block5.ID())
require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom)
......@@ -406,27 +406,27 @@ func TestFastL2Batcher(t *testing.T) {
require.NoError(t, err)
require.Equal(t, l2Block0, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID())
next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block1, derived)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID())
require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block1, next.Derived)
next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block2, derived)
derivedFrom, derived, err = db.NextDerived(l2Block2.ID())
require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block2, next.Derived)
next, err = db.NextDerived(l2Block2.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block3, derived)
derivedFrom, derived, err = db.NextDerived(l2Block3.ID())
require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block3, next.Derived)
next, err = db.NextDerived(l2Block3.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block4, derived)
derivedFrom, derived, err = db.NextDerived(l2Block4.ID())
require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block4, next.Derived)
next, err = db.NextDerived(l2Block4.ID())
require.NoError(t, err)
require.Equal(t, l1Block2, derivedFrom) // derived from later L1 block
require.Equal(t, l2Block5, derived)
_, _, err = db.NextDerived(l2Block5.ID())
require.Equal(t, l1Block2, next.DerivedFrom) // derived from later L1 block
require.Equal(t, l2Block5, next.Derived)
_, err = db.NextDerived(l2Block5.ID())
require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block2.ID())
......@@ -445,10 +445,10 @@ func TestFastL2Batcher(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block2.ID())
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.Equal(t, l1Block1, derivedFrom) // no increment in L1 yet, the next after is L2 block 3
require.Equal(t, l2Block3, derived)
require.Equal(t, l1Block1, next.DerivedFrom) // no increment in L1 yet, the next after is L2 block 3
require.Equal(t, l2Block3, next.Derived)
})
}
......@@ -478,13 +478,13 @@ func TestSlowL2Batcher(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, pair.Derived)
// 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.Equal(t, l2Block2, derived)
......@@ -496,7 +496,7 @@ func TestSlowL2Batcher(t *testing.T) {
}
// 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.Equal(t, l1Block1, derivedFrom)
......@@ -507,15 +507,15 @@ func TestSlowL2Batcher(t *testing.T) {
require.NoError(t, err)
require.Equal(t, l2Block0, derived)
derivedFrom, derived, err = db.NextDerived(l2Block0.ID())
next, err := db.NextDerived(l2Block0.ID())
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block1, derived)
derivedFrom, derived, err = db.NextDerived(l2Block1.ID())
require.Equal(t, l1Block1, next.DerivedFrom)
require.Equal(t, l2Block1, next.Derived)
next, err = db.NextDerived(l2Block1.ID())
require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom)
require.Equal(t, l2Block2, derived)
_, _, err = db.NextDerived(l2Block2.ID())
require.Equal(t, l1Block5, next.DerivedFrom)
require.Equal(t, l2Block2, next.Derived)
_, err = db.NextDerived(l2Block2.ID())
require.ErrorIs(t, err, types.ErrFuture)
derivedFrom, err = db.PreviousDerivedFrom(l1Block5.ID())
......@@ -549,10 +549,10 @@ func TestSlowL2Batcher(t *testing.T) {
_, err = db.NextDerivedFrom(l1Block5.ID())
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.Equal(t, l1Block3, derivedFrom)
require.Equal(t, l2Block1, derived) // no increment in L2 yet, the next after is L1 block 3
require.Equal(t, l1Block3, next.DerivedFrom)
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) {
rng := rand.New(rand.NewSource(1234))
// Insert 1000 randomly generated entries, derived at random bumps in L1
for i := uint64(0); i < 1000; i++ {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
switch rng.Intn(3) {
case 0: // bump L1
derivedFrom = mockL1(derivedFrom.Number + 1)
pair.DerivedFrom = mockL1(pair.DerivedFrom.Number + 1)
case 1: // bump L2
derived = mockL2(derived.Number + 1)
pair.Derived = mockL2(pair.Derived.Number + 1)
case 2: // bump both
derivedFrom = mockL1(derivedFrom.Number + 1)
derived = mockL2(derived.Number + 1)
pair.DerivedFrom = mockL1(pair.DerivedFrom.Number + 1)
pair.Derived = mockL2(pair.Derived.Number + 1)
}
derivedFromRef := toRef(derivedFrom, mockL1(derivedFrom.Number-1).Hash)
derivedRef := toRef(derived, mockL2(derived.Number-1).Hash)
lastDerived[derivedFromRef.ID()] = derived
derivedFromRef := toRef(pair.DerivedFrom, mockL1(pair.DerivedFrom.Number-1).Hash)
derivedRef := toRef(pair.Derived, mockL2(pair.Derived.Number-1).Hash)
lastDerived[derivedFromRef.ID()] = pair.Derived
if _, ok := firstDerivedFrom[derivedRef.ID()]; !ok {
firstDerivedFrom[derivedRef.ID()] = derivedFrom
firstDerivedFrom[derivedRef.ID()] = pair.DerivedFrom
}
require.NoError(t, db.AddDerived(derivedFromRef, derivedRef))
}
}, func(t *testing.T, db *DB, m *stubMetrics) {
// 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.NotZero(t, derivedFrom.Number-offsetL1)
require.NotZero(t, derived.Number-offsetL2)
require.NotZero(t, pair.DerivedFrom.Number-offsetL1)
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()
derived, err := db.LastDerivedAt(l1ID)
require.NoError(t, err)
......@@ -621,7 +621,7 @@ func testManyEntryDB(t *testing.T, offsetL1 uint64, offsetL2 uint64) {
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()
derivedFrom, err := db.DerivedFrom(l2ID)
require.NoError(t, err)
......@@ -673,42 +673,187 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddDerived(toRef(l1Block5, l1Block4.Hash), toRef(l2Block2, l2Block1.Hash)))
}, func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, pair.Derived)
// Rewind to the future
require.ErrorIs(t, db.Rewind(6), types.ErrFuture)
// Rewind to the exact block we're at
require.NoError(t, db.Rewind(l1Block5.Number))
derivedFrom, derived, err = db.Latest()
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block5, derivedFrom)
require.Equal(t, l2Block2, derived)
require.Equal(t, l1Block5, pair.DerivedFrom)
require.Equal(t, l2Block2, pair.Derived)
// Now rewind to L1 block 3 (inclusive).
require.NoError(t, db.Rewind(l1Block3.Number))
// See if we find consistent data
derivedFrom, derived, err = db.Latest()
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block3, derivedFrom)
require.Equal(t, l2Block1, derived)
require.Equal(t, l1Block3, pair.DerivedFrom)
require.Equal(t, l2Block1, pair.Derived)
// Rewind further to L1 block 1 (inclusive).
require.NoError(t, db.Rewind(l1Block1.Number))
derivedFrom, derived, err = db.Latest()
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block1, derivedFrom)
require.Equal(t, l2Block1, derived)
require.Equal(t, l1Block1, pair.DerivedFrom)
require.Equal(t, l2Block1, pair.Derived)
// Rewind further to L1 block 0 (inclusive).
require.NoError(t, db.Rewind(l1Block0.Number))
derivedFrom, derived, err = db.Latest()
pair, err = db.Latest()
require.NoError(t, err)
require.Equal(t, l1Block0, derivedFrom)
require.Equal(t, l2Block0, derived)
require.Equal(t, l1Block0, pair.DerivedFrom)
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
const (
DerivedFromV0 EntryType = 0
InvalidatedFromV0 EntryType = 1
)
func (s EntryType) String() string {
switch s {
case DerivedFromV0:
return "v0"
return "derivedFromV0"
case InvalidatedFromV0:
return "invalidatedFromV0"
default:
return fmt.Sprintf("unknown(%d)", uint8(s))
}
......@@ -45,22 +48,31 @@ func (EntryBinary) EntrySize() int {
return EntrySize
}
// LinkEntry is a DerivedFromV0 or a InvalidatedFromV0 kind
type LinkEntry struct {
derivedFrom 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 {
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 {
if e.Type() != DerivedFromV0 {
if t := e.Type(); t != DerivedFromV0 && t != InvalidatedFromV0 {
return fmt.Errorf("%w: unexpected entry type: %s", types.ErrDataCorruption, e.Type())
}
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])
}
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
d.derivedFrom.Number = binary.BigEndian.Uint64(e[offset : offset+8])
offset += 8
......@@ -78,7 +90,11 @@ func (d *LinkEntry) decode(e Entry) error {
func (d *LinkEntry) encode() Entry {
var out Entry
if d.invalidated {
out[0] = uint8(InvalidatedFromV0)
} else {
out[0] = uint8(DerivedFromV0)
}
offset := 4
binary.BigEndian.PutUint64(out[offset:offset+8], d.derivedFrom.Number)
offset += 8
......@@ -93,3 +109,13 @@ func (d *LinkEntry) encode() Entry {
copy(out[offset:offset+32], d.derived.Hash[:])
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
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -10,9 +12,86 @@ import (
func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
db.rwLock.Lock()
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
if db.store.Size() == 0 {
// ReplaceInvalidatedBlock replaces the current Invalidated block with the given replacement.
// 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{
derivedFrom: types.BlockSeal{
Hash: derivedFrom.Hash,
......@@ -24,6 +103,12 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
Number: derived.Number,
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()
if err := db.store.Append(e); err != nil {
......@@ -33,10 +118,15 @@ func (db *DB) AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error {
return nil
}
lastDerivedFrom, lastDerived, err := db.latest()
last, err := db.latest()
if err != nil {
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() {
// 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 {
if lastDerived.Number == derived.Number {
// 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.
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 {
return fmt.Errorf("derived block %s conflicts with known derived block %s at same height: %w",
derived, lastDerived, types.ErrConflict)
}
}
} else if lastDerived.Number+1 == derived.Number {
if lastDerived.Hash != derived.ParentHash {
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 {
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()
if err := db.store.Append(e); err != nil {
return err
......
......@@ -31,10 +31,10 @@ func TestBadUpdates(t *testing.T) {
fDerived := mockL2(206)
noChange := assertFn(func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, dDerivedFrom, derivedFrom)
require.Equal(t, dDerived, derived)
require.Equal(t, dDerivedFrom, pair.DerivedFrom)
require.Equal(t, dDerived, pair.Derived)
})
testCases := []testCase{
......@@ -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)
},
assertFn: func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, dDerivedFrom, derivedFrom)
require.Equal(t, eDerived, derived)
require.Equal(t, dDerivedFrom, pair.DerivedFrom)
require.Equal(t, eDerived, pair.Derived)
},
},
{
......@@ -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)
},
assertFn: func(t *testing.T, db *DB, m *stubMetrics) {
derivedFrom, derived, err := db.Latest()
pair, err := db.Latest()
require.NoError(t, err)
require.Equal(t, eDerivedFrom, derivedFrom)
require.Equal(t, dDerived, derived)
require.Equal(t, eDerivedFrom, pair.DerivedFrom)
require.Equal(t, dDerived, pair.Derived)
},
},
{
......
......@@ -248,18 +248,22 @@ func (db *DB) OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, exe
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)
func (db *DB) LatestSealedBlockNum() (n uint64, ok bool) {
func (db *DB) LatestSealedBlock() (id eth.BlockID, ok bool) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
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() {
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)
......@@ -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
// The block at headBlockNum itself is not removed.
func (db *DB) Rewind(newHeadBlockNum uint64) error {
// The block at newHead.Number itself is not removed.
func (db *DB) Rewind(newHead eth.BlockID) error {
db.rwLock.Lock()
defer db.rwLock.Unlock()
// Even if the last fully-processed block matches headBlockNum,
// 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 {
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,
// this deletes everything after idx
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
if err := db.init(true); err != nil {
......
......@@ -19,6 +19,13 @@ import (
"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 {
if i == -1 { // parent-hash of genesis is zero
return common.Hash{}
......@@ -85,9 +92,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
runDBTest(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.False(t, ok, "empty db expected")
require.Zero(t, n)
require.Equal(t, eth.BlockID{}, head)
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")
......@@ -101,9 +108,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
},
func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum()
head, ok := db.LatestSealedBlock()
require.True(t, ok, "genesis block expected")
require.Equal(t, genesis.Number, n)
require.Equal(t, genesis, head)
idx, err := db.searchCheckpoint(0, 0)
require.NoError(t, err)
require.Zero(t, idx, "genesis block as checkpoint 0")
......@@ -124,9 +131,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
},
func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum()
head, ok := db.LatestSealedBlock()
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)
require.NoError(t, err)
require.Zero(t, idx, "anchor block as checkpoint 0")
......@@ -152,9 +159,9 @@ func TestLatestSealedBlockNum(t *testing.T) {
require.NoError(t, db.SealBlock(genesis.Hash, block1, 5001), "seal block 1")
},
func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum()
head, ok := db.LatestSealedBlock()
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)
require.NoError(t, err)
require.Equal(t, entrydb.EntryIdx(0), idx, "checkpoint 0 still for block 1")
......@@ -187,10 +194,10 @@ func TestLatestSealedBlockNum(t *testing.T) {
}
},
func(t *testing.T, db *DB, m *stubMetrics) {
n, ok := db.LatestSealedBlockNum()
head, ok := db.LatestSealedBlock()
require.True(t, ok, "latest block expected")
expected := uint64(260)
require.Equal(t, expected, n)
require.Equal(t, expected, head.Number)
idx, err := db.searchCheckpoint(expected, 0)
require.NoError(t, err)
// 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) {
require.Equal(t, entrydb.EntryIdx(searchCheckpointFrequency*2), idx, "checkpoint 1 reached")
// 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.Empty(t, execMsgs)
require.Zero(t, logCount)
require.Equal(t, createHash(int(n)), ref.Hash)
require.Equal(t, uint64(5000)+n, ref.Time)
require.Equal(t, head.Hash, ref.Hash)
require.Equal(t, uint64(5000)+head.Number, ref.Time)
})
})
}
......@@ -1017,11 +1024,11 @@ func TestRewind(t *testing.T) {
t.Run("WhenEmpty", func(t *testing.T) {
runDBTest(t, 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(100), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(100)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(100)), types.ErrFuture)
// Genesis is a block to, not present in an empty DB
require.ErrorIs(t, db.Rewind(0), types.ErrFuture)
require.ErrorIs(t, db.Rewind(0), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(0)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(0)), types.ErrFuture)
})
})
......@@ -1039,8 +1046,8 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
require.NoError(t, db.AddLog(createHash(4), bl52, 0, nil))
// cannot rewind to a block that is not sealed yet
require.ErrorIs(t, db.Rewind(53), types.ErrFuture)
require.ErrorIs(t, db.Rewind(53), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(53)), types.ErrFuture)
require.ErrorIs(t, db.Rewind(createID(53)), types.ErrFuture)
},
func(t *testing.T, db *DB, m *stubMetrics) {
requireContains(t, db, 51, 0, createHash(1))
......@@ -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(2), bl50, 1, nil))
// cannot go back to an unknown block
require.ErrorIs(t, db.Rewind(25), types.ErrSkipped)
require.ErrorIs(t, db.Rewind(25), types.ErrSkipped)
require.ErrorIs(t, db.Rewind(createID(25)), types.ErrSkipped)
require.ErrorIs(t, db.Rewind(createID(25)), types.ErrSkipped)
},
func(t *testing.T, db *DB, m *stubMetrics) {
// block 51 is not sealed yet
......@@ -1082,7 +1089,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
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) {
requireContains(t, db, 51, 0, createHash(1))
......@@ -1111,7 +1118,7 @@ func TestRewind(t *testing.T) {
require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
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) {
require.EqualValues(t, searchCheckpointFrequency+2+2, m.entryCount, "Should have deleted second checkpoint")
......@@ -1134,7 +1141,7 @@ func TestRewind(t *testing.T) {
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) {
requireContains(t, db, 15, 0, createHash(1))
......@@ -1157,7 +1164,7 @@ func TestRewind(t *testing.T) {
}
}
// 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) {
requireContains(t, db, 20, 0, createHash(1))
......@@ -1180,7 +1187,7 @@ func TestRewind(t *testing.T) {
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) {
bl29 := eth.BlockID{Hash: createHash(29), Number: 29}
......
......@@ -27,33 +27,35 @@ func (db *ChainsDB) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) {
if !knownChain {
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.
// 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
func (db *ChainsDB) LastCommonL1() (types.BlockSeal, error) {
common := types.BlockSeal{}
commonL1 := types.BlockSeal{}
for _, chain := range db.depSet.Chains() {
ldb, ok := db.localDBs.Get(chain)
if !ok {
return types.BlockSeal{}, types.ErrUnknownChain
}
_, derivedFrom, err := ldb.Latest()
last, err := ldb.Latest()
if err != nil {
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,
// or if the new common block is older than the current common block
// set the common block
if common == (types.BlockSeal{}) ||
derivedFrom.Number < common.Number {
common = derivedFrom
if commonL1 == (types.BlockSeal{}) ||
derivedFrom.Number < commonL1.Number {
commonL1 = derivedFrom
}
}
return common, nil
return commonL1, nil
}
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) {
if !ok {
return types.BlockSeal{}, types.ErrUnknownChain
}
n, ok := eventsDB.LatestSealedBlockNum()
head, ok := eventsDB.LatestSealedBlock()
if !ok {
return types.BlockSeal{}, types.ErrFuture
}
return eventsDB.FindSealedBlock(n)
return eventsDB.FindSealedBlock(head.Number)
}
func (db *ChainsDB) CrossUnsafe(chainID eth.ChainID) (types.BlockSeal, error) {
......@@ -149,8 +151,7 @@ func (db *ChainsDB) LocalSafe(chainID eth.ChainID) (pair types.DerivedBlockSealP
if !ok {
return types.DerivedBlockSealPair{}, types.ErrUnknownChain
}
df, d, err := localDB.Latest()
return types.DerivedBlockSealPair{DerivedFrom: df, Derived: d}, err
return localDB.Latest()
}
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
if !ok {
return types.DerivedBlockSealPair{}, types.ErrUnknownChain
}
df, d, err := crossDB.Latest()
return types.DerivedBlockSealPair{DerivedFrom: df, Derived: d}, err
return crossDB.Latest()
}
func (db *ChainsDB) FinalizedL1() eth.BlockRef {
......@@ -177,19 +177,19 @@ func (db *ChainsDB) Finalized(chainID eth.ChainID) (types.BlockSeal, error) {
if !ok {
return types.BlockSeal{}, types.ErrUnknownChain
}
latestDerivedFrom, latestDerived, err := xDB.Latest()
latest, err := xDB.Latest()
if err != nil {
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,
// 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",
"chain", chainID,
"finalizedL1", finalizedL1.Number,
"latestDerivedFrom", latestDerivedFrom.Number,
"latestDerived", latestDerivedFrom)
return latestDerived, nil
"latestDerivedFrom", latest.DerivedFrom.Number,
"latestDerived", latest.DerivedFrom)
return latest.Derived, nil
}
// 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
//
// Or ErrOutOfScope, with non-zero derivedFromScope,
// 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)
if !ok {
return eth.BlockRef{}, eth.BlockRef{}, types.ErrUnknownChain
......@@ -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, 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 errors.Is(err, types.ErrFuture) {
// 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 {
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,
derivedFromRef, err := derivedFrom.WithParent(eth.BlockID{})
derivedFromRef, err := first.DerivedFrom.WithParent(eth.BlockID{})
if err != nil {
// if the first derivedFrom isn't the genesis block, just warn and continue anyway
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
derivedRef := derived.MustWithParent(eth.BlockID{})
derivedRef := first.Derived.MustWithParent(eth.BlockID{})
return derivedFromRef, derivedRef, nil
}
return eth.BlockRef{}, eth.BlockRef{}, err
......@@ -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,
// And update cross-safe accordingly.
// 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 {
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
// 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) {
parentDerivedFrom = types.BlockSeal{}
} 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.
if candidateFrom.Number > crossDerivedFrom.Number+1 {
if candidatePair.DerivedFrom.Number > crossSafe.DerivedFrom.Number+1 {
// 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.
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
// 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) {
crossDerivedFromRef = crossDerivedFrom.ForceWithParent(eth.BlockID{})
crossDerivedFromRef = crossSafe.DerivedFrom.ForceWithParent(eth.BlockID{})
} else if err != nil {
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 {
crossDerivedFromRef = crossDerivedFrom.MustWithParent(parent.ID())
crossDerivedFromRef = crossSafe.DerivedFrom.MustWithParent(parent.ID())
}
return crossDerivedFromRef, eth.BlockRef{},
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
}
......
......@@ -40,12 +40,12 @@ func (db *ChainsDB) SealBlock(chain eth.ChainID, block eth.BlockRef) error {
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)
if !ok {
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) {
......
......@@ -29,7 +29,7 @@ type LogProcessor 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)
}
......@@ -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
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 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)
......
......@@ -15,6 +15,8 @@ var (
ErrFuture = errors.New("future data")
// ErrConflict happens when we know for sure that there is different canonical 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 = errors.New("iter stop")
// 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 {
// DerivedBlockRefPair is a pair of block refs, where Derived (L2) is derived from DerivedFrom (L1).
type DerivedBlockRefPair struct {
DerivedFrom eth.BlockRef
Derived eth.BlockRef
DerivedFrom eth.BlockRef `json:"derivedFrom"`
Derived eth.BlockRef `json:"derived"`
}
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).
type DerivedBlockSealPair struct {
DerivedFrom BlockSeal
Derived BlockSeal
DerivedFrom BlockSeal `json:"derivedFrom"`
Derived BlockSeal `json:"derived"`
}
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).
type DerivedIDPair struct {
DerivedFrom eth.BlockID
Derived eth.BlockID
DerivedFrom eth.BlockID `json:"derivedFrom"`
Derived eth.BlockID `json:"derived"`
}
// 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