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) {
......
...@@ -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