Commit 58793000 authored by Axel Kingsley's avatar Axel Kingsley Committed by GitHub

Interop: XSafe Head Maintainer (#11458)

* WIP: Cross-Head Maintenance

* Add NextExecutingMessage ; Add ChainsDB Tests

* Add Tests for SafetyCheckers

* spelling

* correct test

* add safety_checkers_test.go

* Address Coments From Proto
parent 01306200
......@@ -6,6 +6,9 @@ import (
"io"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/heads"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
backendTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)
......@@ -20,11 +23,18 @@ type LogStorage interface {
Rewind(newHeadBlockNum uint64) error
LatestBlockNum() uint64
ClosestBlockInfo(blockNum uint64) (uint64, backendTypes.TruncatedHash, error)
Contains(blockNum uint64, logIdx uint32, loghash backendTypes.TruncatedHash) (bool, entrydb.EntryIdx, error)
LastCheckpointBehind(entrydb.EntryIdx) (logs.Iterator, error)
NextExecutingMessage(logs.Iterator) (backendTypes.ExecutingMessage, error)
}
type HeadsStorage interface {
Current() *heads.Heads
Apply(op heads.Operation) error
}
// ChainsDB is a database that stores logs and heads for multiple chains.
// it implements the ChainsStorage interface.
type ChainsDB struct {
logDBs map[types.ChainID]LogStorage
heads HeadsStorage
......@@ -49,6 +59,79 @@ func (db *ChainsDB) Resume() error {
return nil
}
// UpdateCrossSafeHeads updates the cross-heads of all chains
// this is an example of how to use the SafetyChecker to update the cross-heads
func (db *ChainsDB) UpdateCrossSafeHeads() error {
checker := NewSafetyChecker(Safe, *db)
return db.UpdateCrossHeads(checker)
}
// UpdateCrossHeadsForChain updates the cross-head for a single chain.
// the provided checker controls which heads are considered.
// TODO: we should invert control and have the underlying logDB call their own update
// for now, monolithic control is fine. There may be a stronger reason to refactor if the API needs it.
func (db *ChainsDB) UpdateCrossHeadsForChain(chainID types.ChainID, checker SafetyChecker) error {
// start with the xsafe head of the chain
xHead := checker.CrossHeadForChain(chainID)
// advance as far as the local head
localHead := checker.LocalHeadForChain(chainID)
// get an iterator for the last checkpoint behind the x-head
i, err := db.logDBs[chainID].LastCheckpointBehind(xHead)
if err != nil {
return fmt.Errorf("failed to rewind cross-safe head for chain %v: %w", chainID, err)
}
// advance the logDB through all executing messages we can
// this loop will break:
// - when we reach the local head
// - when we reach a message that is not safe
// - if an error occurs
for {
exec, err := db.logDBs[chainID].NextExecutingMessage(i)
if err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("failed to read next executing message for chain %v: %w", chainID, err)
}
// if we are now beyond the local head, stop
if i.Index() > localHead {
break
}
// use the checker to determine if this message is safe
safe := checker.Check(
types.ChainIDFromUInt64(uint64(exec.Chain)),
exec.BlockNum,
exec.LogIdx,
exec.Hash)
if !safe {
break
}
// if all is well, prepare the x-head update to this point
xHead = i.Index()
}
// have the checker create an update to the x-head in question, and apply that update
err = db.heads.Apply(checker.Update(chainID, xHead))
if err != nil {
return fmt.Errorf("failed to update cross-head for chain %v: %w", chainID, err)
}
return nil
}
// UpdateCrossSafeHeads updates the cross-heads of all chains
// based on the provided SafetyChecker. The SafetyChecker is used to determine
// the safety of each log entry in the database, and the cross-head associated with it.
func (db *ChainsDB) UpdateCrossHeads(checker SafetyChecker) error {
currentHeads := db.heads.Current()
for chainID := range currentHeads.Chains {
if err := db.UpdateCrossHeadsForChain(chainID, checker); err != nil {
return err
}
}
return nil
}
// LatestBlockNum returns the latest block number that has been recorded to the logs db
// for the given chain. It does not contain safety guarantees.
func (db *ChainsDB) LatestBlockNum(chain types.ChainID) uint64 {
logDB, ok := db.logDBs[chain]
if !ok {
......
......@@ -6,6 +6,8 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/logs"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types"
"github.com/stretchr/testify/require"
)
......@@ -48,6 +50,14 @@ type stubLogStore struct {
rewoundTo uint64
}
func (s *stubLogStore) Contains(blockNum uint64, logIdx uint32, loghash types.TruncatedHash) (bool, entrydb.EntryIdx, error) {
panic("not supported")
}
func (s *stubLogStore) LastCheckpointBehind(entrydb.EntryIdx) (logs.Iterator, error) {
panic("not supported")
}
func (s *stubLogStore) ClosestBlockInfo(blockNum uint64) (uint64, types.TruncatedHash, error) {
if s.closestBlockErr != nil {
return 0, types.TruncatedHash{}, s.closestBlockErr
......@@ -55,6 +65,10 @@ func (s *stubLogStore) ClosestBlockInfo(blockNum uint64) (uint64, types.Truncate
return s.closestBlockNumber, types.TruncatedHash{}, nil
}
func (s *stubLogStore) NextExecutingMessage(logs.Iterator) (types.ExecutingMessage, error) {
panic("not supported")
}
func (s *stubLogStore) Rewind(headBlockNum uint64) error {
s.rewoundTo = headBlockNum
return nil
......
......@@ -202,29 +202,42 @@ func (db *DB) ClosestBlockInfo(blockNum uint64) (uint64, types.TruncatedHash, er
return checkpoint.blockNum, entry.hash, nil
}
// Get returns the truncated hash of the log at the specified blockNum and logIdx,
// or an error if the log is not found.
func (db *DB) Get(blockNum uint64, logiIdx uint32) (types.TruncatedHash, error) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
hash, _, err := db.findLogInfo(blockNum, logiIdx)
return hash, err
}
// Contains return true iff the specified logHash is recorded in the specified blockNum and logIdx.
// logIdx is the index of the log in the array of all logs the block.
// If the log is found, the entry index of the log is returned, too.
// 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.
func (db *DB) Contains(blockNum uint64, logIdx uint32, logHash types.TruncatedHash) (bool, error) {
func (db *DB) Contains(blockNum uint64, logIdx uint32, logHash types.TruncatedHash) (bool, entrydb.EntryIdx, error) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
db.log.Trace("Checking for log", "blockNum", blockNum, "logIdx", logIdx, "hash", logHash)
evtHash, _, err := db.findLogInfo(blockNum, logIdx)
evtHash, iter, err := db.findLogInfo(blockNum, logIdx)
if errors.Is(err, ErrNotFound) {
// Did not find a log at blockNum and logIdx
return false, nil
return false, 0, nil
} else if err != nil {
return false, err
return false, 0, err
}
db.log.Trace("Found initiatingEvent", "blockNum", blockNum, "logIdx", logIdx, "hash", evtHash)
// Found the requested block and log index, check if the hash matches
return evtHash == logHash, nil
if evtHash == logHash {
return true, iter.Index(), nil
}
return false, 0, nil
}
// Executes checks if the log identified by the specific block number and log index, has an ExecutingMessage associated
// with it that needs to be checked as part of interop validation.
// logIdx is the index of the log in the array of all logs the block.
// logIdx is the index of the log in the array of all logs in the block.
// Returns the ExecutingMessage if it exists, or ExecutingMessage{} if the log is found but has no ExecutingMessage.
// Returns ErrNotFound if the specified log does not exist in the database.
func (db *DB) Executes(blockNum uint64, logIdx uint32) (types.ExecutingMessage, error) {
......@@ -241,7 +254,7 @@ func (db *DB) Executes(blockNum uint64, logIdx uint32) (types.ExecutingMessage,
return execMsg, nil
}
func (db *DB) findLogInfo(blockNum uint64, logIdx uint32) (types.TruncatedHash, *iterator, error) {
func (db *DB) findLogInfo(blockNum uint64, logIdx uint32) (types.TruncatedHash, Iterator, error) {
entryIdx, err := db.searchCheckpoint(blockNum, logIdx)
if errors.Is(err, io.EOF) {
// Did not find a checkpoint to start reading from so the log cannot be present.
......@@ -477,6 +490,58 @@ func (db *DB) Rewind(headBlockNum uint64) error {
return nil
}
// NextExecutingMessage returns the next executing message in the log database.
// it skips over any non-executing messages, and will return an error if encountered.
// the iterator is modified in the process.
func (db *DB) NextExecutingMessage(iter Iterator) (types.ExecutingMessage, error) {
db.rwLock.RLock()
defer db.rwLock.RUnlock()
// this for-loop will break:
// - when the iterator reaches the end of the log
// - when the iterator reaches an executing message
// - if an error occurs
for {
_, _, _, err := iter.NextLog()
if err != nil {
return types.ExecutingMessage{}, err
}
// if the log is not an executing message, both exec and err are empty
exec, err := iter.ExecMessage()
if err != nil {
return types.ExecutingMessage{}, fmt.Errorf("failed to get executing message: %w", err)
}
if exec != (types.ExecutingMessage{}) {
return exec, nil
}
}
}
// LastCheckpointBehind returns an iterator for the last checkpoint behind the specified entry index.
// If the entry index is a search checkpoint, the iterator will start at that checkpoint.
// After searching back long enough (the searchCheckpointFrequency), an error is returned,
// as checkpoints are expected to be found within the frequency.
func (db *DB) LastCheckpointBehind(entryIdx entrydb.EntryIdx) (Iterator, error) {
for attempts := 0; attempts < searchCheckpointFrequency; attempts++ {
// attempt to read the index entry as a search checkpoint
_, err := db.readSearchCheckpoint(entryIdx)
if err == nil {
return db.newIterator(entryIdx)
}
// ErrDataCorruption is the return value if the entry is not a search checkpoint
// if it's not that type of error, we should return it instead of continuing
if !errors.Is(err, ErrDataCorruption) {
return nil, err
}
// don't attempt to read behind the start of the data
if entryIdx == 0 {
break
}
// reverse if we haven't found it yet
entryIdx -= 1
}
return nil, fmt.Errorf("failed to find a search checkpoint in the last %v entries", searchCheckpointFrequency)
}
func (db *DB) readSearchCheckpoint(entryIdx entrydb.EntryIdx) (searchCheckpoint, error) {
data, err := db.store.Read(entryIdx)
if err != nil {
......
......@@ -548,7 +548,7 @@ func requireContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHa
require.LessOrEqual(t, len(execMsg), 1, "cannot have multiple executing messages for a single log")
m, ok := db.m.(*stubMetrics)
require.True(t, ok, "Did not get the expected metrics type")
result, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
result, _, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum)
require.Truef(t, result, "Did not find log %v in block %v with hash %v", logIdx, blockNum, logHash)
require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints")
......@@ -564,7 +564,7 @@ func requireContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHa
func requireNotContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash) {
m, ok := db.m.(*stubMetrics)
require.True(t, ok, "Did not get the expected metrics type")
result, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
result, _, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum)
require.Falsef(t, result, "Found unexpected log %v in block %v with hash %v", logIdx, blockNum, logHash)
require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency), "Should not need to read more than between two checkpoints")
......@@ -584,10 +584,10 @@ func requireExecutingMessage(t *testing.T, db *DB, blockNum uint64, logIdx uint3
require.NotZero(t, m.entriesReadForSearch, "Must read at least some entries to find the log")
}
func requireWrongHash(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash, execMsg types.ExecutingMessage) {
func requireWrongHash(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash, _ types.ExecutingMessage) {
m, ok := db.m.(*stubMetrics)
require.True(t, ok, "Did not get the expected metrics type")
result, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
result, _, err := db.Contains(blockNum, logIdx, types.TruncateHash(logHash))
require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum)
require.Falsef(t, result, "Found unexpected log %v in block %v with hash %v", logIdx, blockNum, logHash)
......
......@@ -9,6 +9,12 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/types"
)
type Iterator interface {
NextLog() (blockNum uint64, logIdx uint32, evtHash types.TruncatedHash, outErr error)
Index() entrydb.EntryIdx
ExecMessage() (types.ExecutingMessage, error)
}
type iterator struct {
db *DB
nextEntryIdx entrydb.EntryIdx
......@@ -63,6 +69,10 @@ func (i *iterator) NextLog() (blockNum uint64, logIdx uint32, evtHash types.Trun
return
}
func (i *iterator) Index() entrydb.EntryIdx {
return i.nextEntryIdx - 1
}
func (i *iterator) ExecMessage() (types.ExecutingMessage, error) {
if !i.hasExecMsg {
return types.ExecutingMessage{}, nil
......
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
}
// unsafeChecker is a SafetyChecker that uses the unsafe head as the view into the database
type unsafeChecker struct {
chainsDB ChainsDB
}
// safeChecker is a SafetyChecker that uses the safe head as the view into the database
type safeChecker struct {
chainsDB ChainsDB
}
// finalizedChecker is a SafetyChecker that uses the finalized head as the view into the database
type finalizedChecker struct {
chainsDB ChainsDB
}
// NewSafetyChecker creates a new SafetyChecker of the given type
func NewSafetyChecker(t string, chainsDB ChainsDB) SafetyChecker {
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")
}
}
// 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
}
// 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(
chainsDB ChainsDB,
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
}
}
package db
import (
"fmt"
"testing"
"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"
"github.com/stretchr/testify/require"
)
// TestHeadsForChain tests the heads for a chain,
// confirming the Unsafe, Safe and Finalized all return the correct head for the chain.
// and confirming that the chainID matters when finding the value
func TestHeadsForChain(t *testing.T) {
h := heads.NewHeads()
chainHeads := heads.ChainHeads{
Unsafe: entrydb.EntryIdx(1),
CrossUnsafe: entrydb.EntryIdx(2),
LocalSafe: entrydb.EntryIdx(3),
CrossSafe: entrydb.EntryIdx(4),
LocalFinalized: entrydb.EntryIdx(5),
CrossFinalized: entrydb.EntryIdx(6),
}
h.Put(types.ChainIDFromUInt64(1), chainHeads)
chainsDB := NewChainsDB(nil, &stubHeadStorage{h})
tcases := []struct {
name string
chainID types.ChainID
checkerType string
expectedLocal entrydb.EntryIdx
expectedCross entrydb.EntryIdx
}{
{
"Unsafe Head",
types.ChainIDFromUInt64(1),
Unsafe,
entrydb.EntryIdx(1),
entrydb.EntryIdx(2),
},
{
"Safe Head",
types.ChainIDFromUInt64(1),
Safe,
entrydb.EntryIdx(3),
entrydb.EntryIdx(4),
},
{
"Finalized Head",
types.ChainIDFromUInt64(1),
Finalized,
entrydb.EntryIdx(5),
entrydb.EntryIdx(6),
},
{
"Incorrect Chain",
types.ChainIDFromUInt64(100),
Safe,
entrydb.EntryIdx(0),
entrydb.EntryIdx(0),
},
}
for _, c := range tcases {
t.Run(c.name, func(t *testing.T) {
checker := NewSafetyChecker(c.checkerType, *chainsDB)
localHead := checker.LocalHeadForChain(c.chainID)
crossHead := checker.CrossHeadForChain(c.chainID)
require.Equal(t, c.expectedLocal, localHead)
require.Equal(t, c.expectedCross, crossHead)
})
}
}
func TestCheck(t *testing.T) {
h := heads.NewHeads()
chainHeads := heads.ChainHeads{
Unsafe: entrydb.EntryIdx(6),
CrossUnsafe: entrydb.EntryIdx(5),
LocalSafe: entrydb.EntryIdx(4),
CrossSafe: entrydb.EntryIdx(3),
LocalFinalized: entrydb.EntryIdx(2),
CrossFinalized: entrydb.EntryIdx(1),
}
h.Put(types.ChainIDFromUInt64(1), chainHeads)
// the logStore contains just a single stubbed log DB
logDB := &stubLogDB{}
logsStore := map[types.ChainID]LogStorage{
types.ChainIDFromUInt64(1): logDB,
}
chainsDB := NewChainsDB(logsStore, &stubHeadStorage{h})
tcases := []struct {
name string
checkerType string
chainID types.ChainID
blockNum uint64
logIdx uint32
loghash backendTypes.TruncatedHash
containsResponse containsResponse
expected bool
}{
{
// confirm that checking Unsafe uses the unsafe head,
// and that we can find logs even *at* the unsafe head index
"Unsafe Log at Head",
Unsafe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(6), nil},
true,
},
{
// confirm that checking the Safe head works
"Safe Log",
Safe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(3), nil},
true,
},
{
// confirm that checking the Finalized head works
"Finalized Log",
Finalized,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(1), nil},
true,
},
{
// confirm that when exists is false, we return false
"Does not Exist",
Safe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{false, entrydb.EntryIdx(1), nil},
false,
},
{
// confirm that when a head is out of range, we return false
"Unsafe Out of Range",
Unsafe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(100), nil},
false,
},
{
// confirm that when a head is out of range, we return false
"Safe Out of Range",
Safe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(5), nil},
false,
},
{
// confirm that when a head is out of range, we return false
"Finalized Out of Range",
Finalized,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{true, entrydb.EntryIdx(3), nil},
false,
},
{
// confirm that when Contains returns an error, we return false
"Error",
Safe,
types.ChainIDFromUInt64(1),
1,
1,
backendTypes.TruncatedHash{1, 2, 3},
containsResponse{false, entrydb.EntryIdx(0), fmt.Errorf("error")},
false,
},
}
for _, c := range tcases {
t.Run(c.name, func(t *testing.T) {
// rig the logStore to return the expected response
logDB.containsResponse = c.containsResponse
checker := NewSafetyChecker(c.checkerType, *chainsDB)
r := checker.Check(c.chainID, c.blockNum, c.logIdx, c.loghash)
// confirm that the expected outcome is correct
require.Equal(t, c.expected, r)
})
}
}
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