db.go 7.48 KB
Newer Older
1 2 3 4 5 6 7
package db

import (
	"errors"
	"fmt"
	"io"

8 9 10
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/log"

11
	"github.com/ethereum-optimism/optimism/op-node/rollup/event"
12
	"github.com/ethereum-optimism/optimism/op-service/eth"
13
	"github.com/ethereum-optimism/optimism/op-service/locks"
14
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/fromda"
15
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
16
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
17
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/superevents"
18
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
19 20
)

21 22
type LogStorage interface {
	io.Closer
23

24 25
	AddLog(logHash common.Hash, parentBlock eth.BlockID,
		logIdx uint32, execMsg *types.ExecutingMessage) error
26 27 28

	SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error

29
	Rewind(newHeadBlockNum uint64) error
30 31 32

	LatestSealedBlockNum() (n uint64, ok bool)

33 34 35 36
	// FindSealedBlock finds the requested block by number, to check if it exists,
	// returning the block seal if it was found.
	// returns ErrFuture if the block is too new to be able to tell.
	FindSealedBlock(number uint64) (block types.BlockSeal, err error)
37

38
	IteratorStartingAt(sealedNum uint64, logsSince uint32) (logs.Iterator, error)
39

40 41 42 43 44 45 46 47
	// Contains returns no error iff the specified logHash is recorded in the specified blockNum and logIdx.
	// If the log is out of reach, then ErrFuture is returned.
	// If the log is determined to conflict with the canonical chain, then ErrConflict is returned.
	// logIdx is the index of the log in the array of all logs in the block.
	// This can be used to check the validity of cross-chain interop events.
	// The block-seal of the blockNum block, that the log was included in, is returned.
	// This seal may be fully zeroed, without error, if the block isn't fully known yet.
	Contains(blockNum uint64, logIdx uint32, logHash common.Hash) (includedIn types.BlockSeal, err error)
48 49 50

	// OpenBlock accumulates the ExecutingMessage events for a block and returns them
	OpenBlock(blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error)
51 52 53
}

type LocalDerivedFromStorage interface {
54
	First() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error)
55
	Latest() (derivedFrom types.BlockSeal, derived types.BlockSeal, err error)
56
	AddDerived(derivedFrom eth.BlockRef, derived eth.BlockRef) error
57 58
	LastDerivedAt(derivedFrom eth.BlockID) (derived types.BlockSeal, err error)
	DerivedFrom(derived eth.BlockID) (derivedFrom types.BlockSeal, err error)
59 60 61 62 63
	FirstAfter(derivedFrom, derived eth.BlockID) (nextDerivedFrom, nextDerived 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)
	PreviousDerivedFrom(derivedFrom eth.BlockID) (prevDerivedFrom types.BlockSeal, err error)
	PreviousDerived(derived eth.BlockID) (prevDerived types.BlockSeal, err error)
64 65
}

66 67
var _ LocalDerivedFromStorage = (*fromda.DB)(nil)

68 69 70
type CrossDerivedFromStorage interface {
	LocalDerivedFromStorage
	// This will start to differ with reorg support
71 72
}

73 74
var _ LogStorage = (*logs.DB)(nil)

75
// ChainsDB is a database that stores logs and derived-from data for multiple chains.
76
// it implements the LogStorage interface, as well as several DB interfaces needed by the cross package.
77
type ChainsDB struct {
78
	// unsafe info: the sequence of block seals and events
79
	logDBs locks.RWMap[eth.ChainID, LogStorage]
80 81

	// cross-unsafe: how far we have processed the unsafe data.
82
	// If present but set to a zeroed value the cross-unsafe will fallback to cross-safe.
83
	crossUnsafe locks.RWMap[eth.ChainID, *locks.RWValue[types.BlockSeal]]
84 85

	// local-safe: index of what we optimistically know about L2 blocks being derived from L1
86
	localDBs locks.RWMap[eth.ChainID, LocalDerivedFromStorage]
87 88

	// cross-safe: index of L2 blocks we know to only have cross-L2 valid dependencies
89
	crossDBs locks.RWMap[eth.ChainID, CrossDerivedFromStorage]
90 91 92 93

	// finalized: the L1 finality progress. This can be translated into what may be considered as finalized in L2.
	// It is initially zeroed, and the L2 finality query will return
	// an error until it has this L1 finality to work with.
94
	finalizedL1 locks.RWValue[eth.L1BlockRef]
95

96 97 98 99
	// depSet is the dependency set, used to determine what may be tracked,
	// what is missing, and to provide it to DB users.
	depSet depset.DependencySet

100
	logger log.Logger
101 102 103

	// emitter used to signal when the DB changes, for other modules to react to
	emitter event.Emitter
104 105
}

106 107
var _ event.AttachEmitter = (*ChainsDB)(nil)

108
func NewChainsDB(l log.Logger, depSet depset.DependencySet) *ChainsDB {
109
	return &ChainsDB{
110 111
		logger: l,
		depSet: depSet,
112 113 114
	}
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
func (db *ChainsDB) AttachEmitter(em event.Emitter) {
	db.emitter = em
}

func (db *ChainsDB) OnEvent(ev event.Event) bool {
	switch x := ev.(type) {
	case superevents.AnchorEvent:
		db.maybeInitEventsDB(x.ChainID, x.Anchor)
		db.maybeInitSafeDB(x.ChainID, x.Anchor)
	case superevents.LocalDerivedEvent:
		db.UpdateLocalSafe(x.ChainID, x.Derived.DerivedFrom, x.Derived.Derived)
	case superevents.FinalizedL1RequestEvent:
		db.onFinalizedL1(x.FinalizedL1)
	default:
		return false
	}
	return true
}

134
func (db *ChainsDB) AddLogDB(chainID eth.ChainID, logDB LogStorage) {
135
	if db.logDBs.Has(chainID) {
136 137 138
		db.logger.Warn("overwriting existing log DB for chain", "chain", chainID)
	}

139
	db.logDBs.Set(chainID, logDB)
140 141
}

142
func (db *ChainsDB) AddLocalDerivedFromDB(chainID eth.ChainID, dfDB LocalDerivedFromStorage) {
143
	if db.localDBs.Has(chainID) {
144 145 146
		db.logger.Warn("overwriting existing local derived-from DB for chain", "chain", chainID)
	}

147
	db.localDBs.Set(chainID, dfDB)
148 149
}

150
func (db *ChainsDB) AddCrossDerivedFromDB(chainID eth.ChainID, dfDB CrossDerivedFromStorage) {
151
	if db.crossDBs.Has(chainID) {
152 153 154
		db.logger.Warn("overwriting existing cross derived-from DB for chain", "chain", chainID)
	}

155
	db.crossDBs.Set(chainID, dfDB)
156 157
}

158
func (db *ChainsDB) AddCrossUnsafeTracker(chainID eth.ChainID) {
159
	if db.crossUnsafe.Has(chainID) {
160
		db.logger.Warn("overwriting existing cross-unsafe tracker for chain", "chain", chainID)
161
	}
162
	db.crossUnsafe.Set(chainID, &locks.RWValue[types.BlockSeal]{})
163 164
}

165 166
// ResumeFromLastSealedBlock prepares the chains db to resume recording events after a restart.
// It rewinds the database to the last block that is guaranteed to have been fully recorded to the database,
167
// to ensure it can resume recording from the first log of the next block.
168
func (db *ChainsDB) ResumeFromLastSealedBlock() error {
169
	var result error
170
	db.logDBs.Range(func(chain eth.ChainID, logStore LogStorage) bool {
171
		headNum, ok := logStore.LatestSealedBlockNum()
172
		if !ok {
173 174
			// db must be empty, nothing to rewind to
			db.logger.Info("Resuming, but found no DB contents", "chain", chain)
175
			return true
176 177 178
		}
		db.logger.Info("Resuming, starting from last sealed block", "head", headNum)
		if err := logStore.Rewind(headNum); err != nil {
179 180
			result = fmt.Errorf("failed to rewind chain %s to sealed block %d", chain, headNum)
			return false
181
		}
182 183 184
		return true
	})
	return result
185 186
}

187 188 189 190
func (db *ChainsDB) DependencySet() depset.DependencySet {
	return db.depSet
}

191 192
func (db *ChainsDB) Close() error {
	var combined error
193
	db.logDBs.Range(func(id eth.ChainID, logDB LogStorage) bool {
194 195
		if err := logDB.Close(); err != nil {
			combined = errors.Join(combined, fmt.Errorf("failed to close log db for chain %v: %w", id, err))
196
		}
197 198
		return true
	})
199
	return combined
200
}