safety_checkers.go 5.74 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
package db

import (
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads"
	backendTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

const (
	Unsafe    = "unsafe"
	Safe      = "safe"
	Finalized = "finalized"
)

// SafetyChecker is an interface for checking the safety of a log entry
// and updating the local head for a chain.
type SafetyChecker interface {
	LocalHeadForChain(chainID types.ChainID) entrydb.EntryIdx
	CrossHeadForChain(chainID types.ChainID) entrydb.EntryIdx
	Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) bool
	Update(chain types.ChainID, index entrydb.EntryIdx) heads.OperationFn
23
	Name() string
Axel Kingsley's avatar
Axel Kingsley committed
24
	SafetyLevel() types.SafetyLevel
25 26 27 28
}

// unsafeChecker is a SafetyChecker that uses the unsafe head as the view into the database
type unsafeChecker struct {
Axel Kingsley's avatar
Axel Kingsley committed
29
	chainsDB *ChainsDB
30 31 32 33
}

// safeChecker is a SafetyChecker that uses the safe head as the view into the database
type safeChecker struct {
Axel Kingsley's avatar
Axel Kingsley committed
34
	chainsDB *ChainsDB
35 36 37 38
}

// finalizedChecker is a SafetyChecker that uses the finalized head as the view into the database
type finalizedChecker struct {
Axel Kingsley's avatar
Axel Kingsley committed
39
	chainsDB *ChainsDB
40 41 42
}

// NewSafetyChecker creates a new SafetyChecker of the given type
Axel Kingsley's avatar
Axel Kingsley committed
43
func NewSafetyChecker(t types.SafetyLevel, chainsDB *ChainsDB) SafetyChecker {
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
	switch t {
	case Unsafe:
		return &unsafeChecker{
			chainsDB: chainsDB,
		}
	case Safe:
		return &safeChecker{
			chainsDB: chainsDB,
		}
	case Finalized:
		return &finalizedChecker{
			chainsDB: chainsDB,
		}
	default:
		panic("unknown safety checker type")
	}
}

62 63 64 65 66 67 68 69 70 71 72 73 74
// Name returns the safety checker type, using the same strings as the constants used in construction
func (c *unsafeChecker) Name() string {
	return Unsafe
}

func (c *safeChecker) Name() string {
	return Safe
}

func (c *finalizedChecker) Name() string {
	return Finalized
}

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
// LocalHeadForChain returns the local head for the given chain
// based on the type of SafetyChecker
func (c *unsafeChecker) LocalHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.Unsafe
}

func (c *safeChecker) LocalHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.LocalSafe
}

func (c *finalizedChecker) LocalHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.LocalFinalized
}

// CrossHeadForChain returns the x-head for the given chain
// based on the type of SafetyChecker
func (c *unsafeChecker) CrossHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.CrossUnsafe
}

func (c *safeChecker) CrossHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.CrossSafe
}

func (c *finalizedChecker) CrossHeadForChain(chainID types.ChainID) entrydb.EntryIdx {
	heads := c.chainsDB.heads.Current().Get(chainID)
	return heads.CrossFinalized
}

Axel Kingsley's avatar
Axel Kingsley committed
109 110 111 112 113 114 115 116 117 118 119 120
func (c *unsafeChecker) SafetyLevel() types.SafetyLevel {
	return types.CrossUnsafe
}

func (c *safeChecker) SafetyLevel() types.SafetyLevel {
	return types.CrossSafe
}

func (c *finalizedChecker) SafetyLevel() types.SafetyLevel {
	return types.CrossFinalized
}

121 122 123
// check checks if the log entry is safe, provided a local head for the chain
// it is used by the individual SafetyCheckers to determine if a log entry is safe
func check(
Axel Kingsley's avatar
Axel Kingsley committed
124
	chainsDB *ChainsDB,
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
	localHead entrydb.EntryIdx,
	chain types.ChainID,
	blockNum uint64,
	logIdx uint32,
	logHash backendTypes.TruncatedHash) bool {

	// for the Check to be valid, the log must:
	// exist at the blockNum and logIdx
	// have a hash that matches the provided hash (implicit in the Contains call), and
	// be less than or equal to the local head for the chain
	exists, index, err := chainsDB.logDBs[chain].Contains(blockNum, logIdx, logHash)
	if err != nil {
		return false
	}
	return exists && index <= localHead
}

// Check checks if the log entry is safe, provided a local head for the chain
// it passes on the local head this checker is concerned with, along with its view of the database
func (c *unsafeChecker) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) bool {
	return check(c.chainsDB, c.LocalHeadForChain(chain), chain, blockNum, logIdx, logHash)
}
func (c *safeChecker) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) bool {
	return check(c.chainsDB, c.LocalHeadForChain(chain), chain, blockNum, logIdx, logHash)
}
func (c *finalizedChecker) Check(chain types.ChainID, blockNum uint64, logIdx uint32, logHash backendTypes.TruncatedHash) bool {
	return check(c.chainsDB, c.LocalHeadForChain(chain), chain, blockNum, logIdx, logHash)
}

// Update creates an Operation that updates the x-head for the chain, given an index to set it to
func (c *unsafeChecker) Update(chain types.ChainID, index entrydb.EntryIdx) heads.OperationFn {
	return func(heads *heads.Heads) error {
		chainHeads := heads.Get(chain)
		chainHeads.CrossUnsafe = index
		heads.Put(chain, chainHeads)
		return nil
	}
}

func (c *safeChecker) Update(chain types.ChainID, index entrydb.EntryIdx) heads.OperationFn {
	return func(heads *heads.Heads) error {
		chainHeads := heads.Get(chain)
		chainHeads.CrossSafe = index
		heads.Put(chain, chainHeads)
		return nil
	}
}

func (c *finalizedChecker) Update(chain types.ChainID, index entrydb.EntryIdx) heads.OperationFn {
	return func(heads *heads.Heads) error {
		chainHeads := heads.Get(chain)
		chainHeads.CrossFinalized = index
		heads.Put(chain, chainHeads)
		return nil
	}
}