Commit e1712a54 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-dispute-mon: Check proposals are supported by on chain data (#9847)

Checks the proposed L2 block was safe when the game was created using the safe head database if available.
If not available for any reason, it falls back to the current behaviour of considering the root valid if it matches the local node.
parent a3ca1f41
...@@ -98,27 +98,29 @@ func (f *FaultDisputeGameContract) GetBlockRange(ctx context.Context) (prestateB ...@@ -98,27 +98,29 @@ func (f *FaultDisputeGameContract) GetBlockRange(ctx context.Context) (prestateB
return return
} }
// GetGameMetadata returns the game's L2 block number, root claim, status, and game duration. // GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, and game duration.
func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rpcblock.Block) (uint64, common.Hash, gameTypes.GameStatus, uint64, error) { func (f *FaultDisputeGameContract) GetGameMetadata(ctx context.Context, block rpcblock.Block) (common.Hash, uint64, common.Hash, gameTypes.GameStatus, uint64, error) {
results, err := f.multiCaller.Call(ctx, block, results, err := f.multiCaller.Call(ctx, block,
f.contract.Call(methodL1Head),
f.contract.Call(methodL2BlockNumber), f.contract.Call(methodL2BlockNumber),
f.contract.Call(methodRootClaim), f.contract.Call(methodRootClaim),
f.contract.Call(methodStatus), f.contract.Call(methodStatus),
f.contract.Call(methodGameDuration)) f.contract.Call(methodGameDuration))
if err != nil { if err != nil {
return 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err) return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to retrieve game metadata: %w", err)
} }
if len(results) != 4 { if len(results) != 5 {
return 0, common.Hash{}, 0, 0, fmt.Errorf("expected 3 results but got %v", len(results)) return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("expected 3 results but got %v", len(results))
} }
l2BlockNumber := results[0].GetBigInt(0).Uint64() l1Head := results[0].GetHash(0)
rootClaim := results[1].GetHash(0) l2BlockNumber := results[1].GetBigInt(0).Uint64()
duration := results[3].GetUint64(0) rootClaim := results[2].GetHash(0)
status, err := gameTypes.GameStatusFromUint8(results[2].GetUint8(0)) status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0))
if err != nil { if err != nil {
return 0, common.Hash{}, 0, 0, fmt.Errorf("failed to convert game status: %w", err) return common.Hash{}, 0, common.Hash{}, 0, 0, fmt.Errorf("failed to convert game status: %w", err)
} }
return l2BlockNumber, rootClaim, status, duration, nil duration := results[4].GetUint64(0)
return l1Head, l2BlockNumber, rootClaim, status, duration, nil
} }
func (f *FaultDisputeGameContract) GetGenesisOutputRoot(ctx context.Context) (common.Hash, error) { func (f *FaultDisputeGameContract) GetGenesisOutputRoot(ctx context.Context) (common.Hash, error) {
......
...@@ -334,17 +334,20 @@ func TestGetSplitDepth(t *testing.T) { ...@@ -334,17 +334,20 @@ func TestGetSplitDepth(t *testing.T) {
func TestGetGameMetadata(t *testing.T) { func TestGetGameMetadata(t *testing.T) {
stubRpc, contract := setupFaultDisputeGameTest(t) stubRpc, contract := setupFaultDisputeGameTest(t)
expectedL1Head := common.Hash{0x0a, 0x0b}
expectedL2BlockNumber := uint64(123) expectedL2BlockNumber := uint64(123)
expectedGameDuration := uint64(456) expectedGameDuration := uint64(456)
expectedRootClaim := common.Hash{0x01, 0x02} expectedRootClaim := common.Hash{0x01, 0x02}
expectedStatus := types.GameStatusChallengerWon expectedStatus := types.GameStatusChallengerWon
block := rpcblock.ByNumber(889) block := rpcblock.ByNumber(889)
stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head})
stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)})
stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim})
stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus}) stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus})
stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedGameDuration}) stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedGameDuration})
l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block) l1Head, l2BlockNumber, rootClaim, status, duration, err := contract.GetGameMetadata(context.Background(), block)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedL1Head, l1Head)
require.Equal(t, expectedL2BlockNumber, l2BlockNumber) require.Equal(t, expectedL2BlockNumber, l2BlockNumber)
require.Equal(t, expectedRootClaim, rootClaim) require.Equal(t, expectedRootClaim, rootClaim)
require.Equal(t, expectedStatus, status) require.Equal(t, expectedStatus, status)
......
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
const metricsLabel = "game_caller_creator" const metricsLabel = "game_caller_creator"
type GameCaller interface { type GameCaller interface {
GetGameMetadata(context.Context, rpcblock.Block) (uint64, common.Hash, types.GameStatus, uint64, error) GetGameMetadata(context.Context, rpcblock.Block) (common.Hash, uint64, common.Hash, types.GameStatus, uint64, error)
GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error) GetAllClaims(context.Context, rpcblock.Block) ([]faultTypes.Claim, error)
BondCaller BondCaller
BalanceCaller BalanceCaller
......
...@@ -51,7 +51,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game ...@@ -51,7 +51,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
e.logger.Error("Failed to create game caller", "err", err) e.logger.Error("Failed to create game caller", "err", err)
continue continue
} }
l2BlockNum, rootClaim, status, duration, err := caller.GetGameMetadata(ctx, rpcblock.ByHash(blockHash)) l1Head, l2BlockNum, rootClaim, status, duration, err := caller.GetGameMetadata(ctx, rpcblock.ByHash(blockHash))
if err != nil { if err != nil {
e.logger.Error("Failed to fetch game metadata", "err", err) e.logger.Error("Failed to fetch game metadata", "err", err)
continue continue
...@@ -63,6 +63,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game ...@@ -63,6 +63,7 @@ func (e *Extractor) enrichGames(ctx context.Context, blockHash common.Hash, game
} }
enrichedGame := &monTypes.EnrichedGameData{ enrichedGame := &monTypes.EnrichedGameData{
GameMetadata: game, GameMetadata: game,
L1Head: l1Head,
L2BlockNumber: l2BlockNum, L2BlockNumber: l2BlockNum,
RootClaim: rootClaim, RootClaim: rootClaim,
Status: status, Status: status,
......
...@@ -191,12 +191,12 @@ type mockGameCaller struct { ...@@ -191,12 +191,12 @@ type mockGameCaller struct {
balanceAddr common.Address balanceAddr common.Address
} }
func (m *mockGameCaller) GetGameMetadata(_ context.Context, _ rpcblock.Block) (uint64, common.Hash, types.GameStatus, uint64, error) { func (m *mockGameCaller) GetGameMetadata(_ context.Context, _ rpcblock.Block) (common.Hash, uint64, common.Hash, types.GameStatus, uint64, error) {
m.metadataCalls++ m.metadataCalls++
if m.metadataErr != nil { if m.metadataErr != nil {
return 0, common.Hash{}, 0, 0, m.metadataErr return common.Hash{}, 0, common.Hash{}, 0, 0, m.metadataErr
} }
return 0, mockRootClaim, 0, 0, nil return common.Hash{0xaa}, 0, mockRootClaim, 0, 0, nil
} }
func (m *mockGameCaller) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]faultTypes.Claim, error) { func (m *mockGameCaller) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]faultTypes.Claim, error) {
......
package extract
import (
"context"
"fmt"
monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type BlockFetcher interface {
HeaderByHash(ctx context.Context, block common.Hash) (*types.Header, error)
}
type L1HeadBlockNumEnricher struct {
client BlockFetcher
}
func NewL1HeadBlockNumEnricher(client BlockFetcher) *L1HeadBlockNumEnricher {
return &L1HeadBlockNumEnricher{client: client}
}
func (e *L1HeadBlockNumEnricher) Enrich(ctx context.Context, _ rpcblock.Block, _ GameCaller, game *monTypes.EnrichedGameData) error {
header, err := e.client.HeaderByHash(ctx, game.L1Head)
if err != nil {
return fmt.Errorf("failed to retrieve header for L1 head block %v: %w", game.L1Head, err)
}
game.L1HeadNum = header.Number.Uint64()
return nil
}
package extract
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
func TestL1HeadEnricher(t *testing.T) {
t.Run("HeaderError", func(t *testing.T) {
client := &stubBlockFetcher{err: errors.New("boom")}
enricher := NewL1HeadBlockNumEnricher(client)
caller := &mockGameCaller{}
game := &types.EnrichedGameData{}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.ErrorIs(t, err, client.err)
})
t.Run("GetBalanceSuccess", func(t *testing.T) {
client := &stubBlockFetcher{num: 5000}
enricher := NewL1HeadBlockNumEnricher(client)
caller := &mockGameCaller{}
game := &types.EnrichedGameData{}
err := enricher.Enrich(context.Background(), rpcblock.Latest, caller, game)
require.NoError(t, err)
require.Equal(t, client.num, game.L1HeadNum)
})
}
type stubBlockFetcher struct {
num uint64
err error
}
func (s *stubBlockFetcher) HeaderByHash(_ context.Context, _ common.Hash) (*gethTypes.Header, error) {
if s.err != nil {
return nil, s.err
}
return &gethTypes.Header{
Number: new(big.Int).SetUint64(s.num),
}, nil
}
...@@ -20,7 +20,7 @@ var ( ...@@ -20,7 +20,7 @@ var (
) )
type OutputValidator interface { type OutputValidator interface {
CheckRootAgreement(ctx context.Context, blockNum uint64, root common.Hash) (bool, common.Hash, error) CheckRootAgreement(ctx context.Context, l1HeadNum uint64, l2BlockNum uint64, root common.Hash) (bool, common.Hash, error)
} }
type ForecastMetrics interface { type ForecastMetrics interface {
...@@ -66,7 +66,7 @@ func (f *forecast) recordBatch(batch monTypes.ForecastBatch) { ...@@ -66,7 +66,7 @@ func (f *forecast) recordBatch(batch monTypes.ForecastBatch) {
func (f *forecast) forecastGame(ctx context.Context, game *monTypes.EnrichedGameData, metrics *monTypes.ForecastBatch) error { func (f *forecast) forecastGame(ctx context.Context, game *monTypes.EnrichedGameData, metrics *monTypes.ForecastBatch) error {
// Check the root agreement. // Check the root agreement.
agreement, expected, err := f.validator.CheckRootAgreement(ctx, game.L2BlockNumber, game.RootClaim) agreement, expected, err := f.validator.CheckRootAgreement(ctx, game.L1HeadNum, game.L2BlockNumber, game.RootClaim)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrRootAgreement, err) return fmt.Errorf("%w: %w", ErrRootAgreement, err)
} }
......
...@@ -323,7 +323,7 @@ type stubOutputValidator struct { ...@@ -323,7 +323,7 @@ type stubOutputValidator struct {
err error err error
} }
func (s *stubOutputValidator) CheckRootAgreement(_ context.Context, _ uint64, rootClaim common.Hash) (bool, common.Hash, error) { func (s *stubOutputValidator) CheckRootAgreement(_ context.Context, _ uint64, _ uint64, rootClaim common.Hash) (bool, common.Hash, error) {
s.calls++ s.calls++
if s.err != nil { if s.err != nil {
return false, common.Hash{}, s.err return false, common.Hash{}, s.err
......
...@@ -103,7 +103,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -103,7 +103,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
} }
func (s *Service) initOutputValidator() { func (s *Service) initOutputValidator() {
s.validator = newOutputValidator(s.metrics, s.rollupClient) s.validator = newOutputValidator(s.logger, s.metrics, s.rollupClient)
} }
func (s *Service) initGameCallerCreator() { func (s *Service) initGameCallerCreator() {
...@@ -118,6 +118,7 @@ func (s *Service) initExtractor() { ...@@ -118,6 +118,7 @@ func (s *Service) initExtractor() {
s.extractor = extract.NewExtractor(s.logger, s.game.CreateContract, s.factoryContract.GetGamesAtOrAfter, s.extractor = extract.NewExtractor(s.logger, s.game.CreateContract, s.factoryContract.GetGamesAtOrAfter,
extract.NewBondEnricher(), extract.NewBondEnricher(),
extract.NewBalanceEnricher(), extract.NewBalanceEnricher(),
extract.NewL1HeadBlockNumEnricher(s.l1Client),
) )
} }
......
...@@ -13,6 +13,8 @@ var ResolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), ...@@ -13,6 +13,8 @@ var ResolvedBondAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128),
type EnrichedGameData struct { type EnrichedGameData struct {
types.GameMetadata types.GameMetadata
L1Head common.Hash
L1HeadNum uint64
L2BlockNumber uint64 L2BlockNumber uint64
RootClaim common.Hash RootClaim common.Hash
Status types.GameStatus Status types.GameStatus
......
...@@ -7,12 +7,14 @@ import ( ...@@ -7,12 +7,14 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
type OutputRollupClient interface { type OutputRollupClient interface {
OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error) OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error)
SafeHeadAtL1Block(ctx context.Context, blockNum uint64) (*eth.SafeHeadResponse, error)
} }
type OutputMetrics interface { type OutputMetrics interface {
...@@ -20,20 +22,22 @@ type OutputMetrics interface { ...@@ -20,20 +22,22 @@ type OutputMetrics interface {
} }
type outputValidator struct { type outputValidator struct {
log log.Logger
metrics OutputMetrics metrics OutputMetrics
client OutputRollupClient client OutputRollupClient
} }
func newOutputValidator(metrics OutputMetrics, client OutputRollupClient) *outputValidator { func newOutputValidator(logger log.Logger, metrics OutputMetrics, client OutputRollupClient) *outputValidator {
return &outputValidator{ return &outputValidator{
log: logger,
metrics: metrics, metrics: metrics,
client: client, client: client,
} }
} }
// CheckRootAgreement validates the specified root claim against the output at the given block number. // CheckRootAgreement validates the specified root claim against the output at the given block number.
func (o *outputValidator) CheckRootAgreement(ctx context.Context, blockNum uint64, rootClaim common.Hash) (bool, common.Hash, error) { func (o *outputValidator) CheckRootAgreement(ctx context.Context, l1HeadNum uint64, l2BlockNum uint64, rootClaim common.Hash) (bool, common.Hash, error) {
output, err := o.client.OutputAtBlock(ctx, blockNum) output, err := o.client.OutputAtBlock(ctx, l2BlockNum)
if err != nil { if err != nil {
// string match as the error comes from the remote server so we can't use Errors.Is sadly. // string match as the error comes from the remote server so we can't use Errors.Is sadly.
if strings.Contains(err.Error(), "not found") { if strings.Contains(err.Error(), "not found") {
...@@ -44,5 +48,20 @@ func (o *outputValidator) CheckRootAgreement(ctx context.Context, blockNum uint6 ...@@ -44,5 +48,20 @@ func (o *outputValidator) CheckRootAgreement(ctx context.Context, blockNum uint6
} }
o.metrics.RecordOutputFetchTime(float64(time.Now().Unix())) o.metrics.RecordOutputFetchTime(float64(time.Now().Unix()))
expected := common.Hash(output.OutputRoot) expected := common.Hash(output.OutputRoot)
return rootClaim == expected, expected, nil rootMatches := rootClaim == expected
if !rootMatches {
return false, expected, nil
}
// If the root matches, also check that l2 block is safe at the L1 head
safeHead, err := o.client.SafeHeadAtL1Block(ctx, l1HeadNum)
if err != nil {
o.log.Warn("Unable to verify proposed block was safe", "l1HeadNum", l1HeadNum, "l2BlockNum", l2BlockNum, "err", err)
// If safe head data isn't available, assume the output root was safe
// Avoids making the dispute mon dependent on safe head db being available
//
return true, expected, nil
}
isSafe := safeHead.SafeHead.Number >= l2BlockNum
return isSafe, expected, nil
} }
...@@ -6,7 +6,9 @@ import ( ...@@ -6,7 +6,9 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -19,37 +21,77 @@ func TestDetector_CheckRootAgreement(t *testing.T) { ...@@ -19,37 +21,77 @@ func TestDetector_CheckRootAgreement(t *testing.T) {
t.Run("OutputFetchFails", func(t *testing.T) { t.Run("OutputFetchFails", func(t *testing.T) {
validator, rollup, metrics := setupOutputValidatorTest(t) validator, rollup, metrics := setupOutputValidatorTest(t)
rollup.err = errors.New("boom") rollup.outputErr = errors.New("boom")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 100, 0, mockRootClaim)
require.ErrorIs(t, err, rollup.err) require.ErrorIs(t, err, rollup.outputErr)
require.Equal(t, common.Hash{}, fetched) require.Equal(t, common.Hash{}, fetched)
require.False(t, agree) require.False(t, agree)
require.Zero(t, metrics.fetchTime) require.Zero(t, metrics.fetchTime)
}) })
t.Run("OutputMismatch", func(t *testing.T) { t.Run("OutputMismatch_Safe", func(t *testing.T) {
validator, _, metrics := setupOutputValidatorTest(t) validator, _, metrics := setupOutputValidatorTest(t)
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, common.Hash{}) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 100, 0, common.Hash{})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched) require.Equal(t, mockRootClaim, fetched)
require.False(t, agree) require.False(t, agree)
require.NotZero(t, metrics.fetchTime) require.NotZero(t, metrics.fetchTime)
}) })
t.Run("OutputMatches", func(t *testing.T) { t.Run("OutputMatches_Safe", func(t *testing.T) {
validator, _, metrics := setupOutputValidatorTest(t) validator, _, metrics := setupOutputValidatorTest(t)
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 0, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 200, 0, mockRootClaim)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched) require.Equal(t, mockRootClaim, fetched)
require.True(t, agree) require.True(t, agree)
require.NotZero(t, metrics.fetchTime) require.NotZero(t, metrics.fetchTime)
}) })
t.Run("OutputMismatch_NotSafe", func(t *testing.T) {
validator, client, metrics := setupOutputValidatorTest(t)
client.safeHeadNum = 99
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 100, 0, common.Hash{})
require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched)
require.False(t, agree)
require.NotZero(t, metrics.fetchTime)
})
t.Run("OutputMatches_SafeHeadError", func(t *testing.T) {
validator, client, metrics := setupOutputValidatorTest(t)
client.safeHeadErr = errors.New("boom")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 200, 0, mockRootClaim)
require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched)
require.True(t, agree) // Assume safe if we can't retrieve the safe head so monitoring isn't dependent on safe head db
require.NotZero(t, metrics.fetchTime)
})
t.Run("OutputMismatch_SafeHeadError", func(t *testing.T) {
validator, client, metrics := setupOutputValidatorTest(t)
client.safeHeadErr = errors.New("boom")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 100, 0, common.Hash{})
require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched)
require.False(t, agree) // Not agreed because the root doesn't match
require.NotZero(t, metrics.fetchTime)
})
t.Run("OutputMatches_NotSafe", func(t *testing.T) {
validator, client, metrics := setupOutputValidatorTest(t)
client.safeHeadNum = 99
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 200, 100, mockRootClaim)
require.NoError(t, err)
require.Equal(t, mockRootClaim, fetched)
require.False(t, agree)
require.NotZero(t, metrics.fetchTime)
})
t.Run("OutputNotFound", func(t *testing.T) { t.Run("OutputNotFound", func(t *testing.T) {
validator, rollup, metrics := setupOutputValidatorTest(t) validator, rollup, metrics := setupOutputValidatorTest(t)
// This crazy error is what we actually get back from the API // This crazy error is what we actually get back from the API
rollup.err = errors.New("failed to get L2 block ref with sync status: failed to determine L2BlockRef of height 42984924, could not get payload: not found") rollup.outputErr = errors.New("failed to get L2 block ref with sync status: failed to determine L2BlockRef of height 42984924, could not get payload: not found")
agree, fetched, err := validator.CheckRootAgreement(context.Background(), 42984924, mockRootClaim) agree, fetched, err := validator.CheckRootAgreement(context.Background(), 100, 42984924, mockRootClaim)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, common.Hash{}, fetched) require.Equal(t, common.Hash{}, fetched)
require.False(t, agree) require.False(t, agree)
...@@ -58,9 +100,10 @@ func TestDetector_CheckRootAgreement(t *testing.T) { ...@@ -58,9 +100,10 @@ func TestDetector_CheckRootAgreement(t *testing.T) {
} }
func setupOutputValidatorTest(t *testing.T) (*outputValidator, *stubRollupClient, *stubOutputMetrics) { func setupOutputValidatorTest(t *testing.T) (*outputValidator, *stubRollupClient, *stubOutputMetrics) {
client := &stubRollupClient{} logger := testlog.Logger(t, log.LvlInfo)
client := &stubRollupClient{safeHeadNum: 99999999999}
metrics := &stubOutputMetrics{} metrics := &stubOutputMetrics{}
validator := newOutputValidator(metrics, client) validator := newOutputValidator(logger, metrics, client)
return validator, client, metrics return validator, client, metrics
} }
...@@ -73,11 +116,24 @@ func (s *stubOutputMetrics) RecordOutputFetchTime(fetchTime float64) { ...@@ -73,11 +116,24 @@ func (s *stubOutputMetrics) RecordOutputFetchTime(fetchTime float64) {
} }
type stubRollupClient struct { type stubRollupClient struct {
blockNum uint64 blockNum uint64
err error outputErr error
safeHeadErr error
safeHeadNum uint64
} }
func (s *stubRollupClient) OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error) { func (s *stubRollupClient) OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error) {
s.blockNum = blockNum s.blockNum = blockNum
return &eth.OutputResponse{OutputRoot: eth.Bytes32(mockRootClaim)}, s.err return &eth.OutputResponse{OutputRoot: eth.Bytes32(mockRootClaim)}, s.outputErr
}
func (s *stubRollupClient) SafeHeadAtL1Block(_ context.Context, _ uint64) (*eth.SafeHeadResponse, error) {
if s.safeHeadErr != nil {
return nil, s.safeHeadErr
}
return &eth.SafeHeadResponse{
SafeHead: eth.BlockID{
Number: s.safeHeadNum,
},
}, nil
} }
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