Commit 2eeb5362 authored by Andreas Bigger's avatar Andreas Bigger

Implement the Output Validation Function

parent a48e53c1
......@@ -6,21 +6,25 @@ import (
"sync"
"time"
abi "github.com/ethereum/go-ethereum/accounts/abi"
bind "github.com/ethereum/go-ethereum/accounts/abi/bind"
common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethclient "github.com/ethereum/go-ethereum/ethclient"
log "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
config "github.com/ethereum-optimism/optimism/op-challenger/config"
metrics "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
bindings "github.com/ethereum-optimism/optimism/op-bindings/bindings"
sources "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/eth"
opclient "github.com/ethereum-optimism/optimism/op-service/client"
txmgr "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
type OutputAPI interface {
OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error)
}
// Challenger contests invalid L2OutputOracle outputs
type Challenger struct {
txMgr txmgr.TxManager
......@@ -35,7 +39,7 @@ type Challenger struct {
l1Client *ethclient.Client
rollupClient *sources.RollupClient
rollupClient OutputAPI
// l2 Output Oracle contract
l2ooContract *bindings.L2OutputOracleCaller
......
......@@ -48,5 +48,5 @@ func TestBuildOutputLogFilter_Fails(t *testing.T) {
// Build the filter
_, err := BuildOutputLogFilter(&l2ooABI)
require.Error(t, err)
require.Equal(t, ErrMissingEvent, err)
require.ErrorIs(t, err, ErrMissingEvent)
}
package challenger
import (
"context"
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
var (
// supportedL2OutputVersion is the version of the L2 output that the challenger supports.
supportedL2OutputVersion = eth.Bytes32{}
// ErrInvalidBlockNumber is returned when the block number of the output does not match the expected block number.
ErrInvalidBlockNumber = errors.New("invalid block number")
// ErrUnsupportedL2OOVersion is returned when the output version is not supported.
ErrUnsupportedL2OOVersion = errors.New("unsupported l2oo version")
)
// ValidateOutput checks that a given output is expected via a trusted rollup node rpc.
// It returns: if the output is correct, the fetched output, error
func (c *Challenger) ValidateOutput(ctx context.Context, l2BlockNumber *big.Int, expected eth.Bytes32) (bool, *eth.Bytes32, error) {
// Fetch the output from the rollup node
ctx, cancel := context.WithTimeout(ctx, c.networkTimeout)
defer cancel()
output, err := c.rollupClient.OutputAtBlock(ctx, l2BlockNumber.Uint64())
if err != nil {
c.log.Error("Failed to fetch output", "blockNum", l2BlockNumber, "err", err)
return false, nil, err
}
// Compare the output root to the expected output root
equalRoots, err := c.compareOutputRoots(output, expected, l2BlockNumber)
if err != nil {
return false, nil, err
}
return equalRoots, &output.OutputRoot, nil
}
// compareOutputRoots compares the output root of the given block number to the expected output root.
func (c *Challenger) compareOutputRoots(received *eth.OutputResponse, expected eth.Bytes32, blockNumber *big.Int) (bool, error) {
if received.Version != supportedL2OutputVersion {
c.log.Error("Unsupported l2 output version", "version", received.Version)
return false, ErrUnsupportedL2OOVersion
}
if received.BlockRef.Number != blockNumber.Uint64() {
c.log.Error("Invalid blockNumber", "expected", blockNumber, "actual", received.BlockRef.Number)
return false, ErrInvalidBlockNumber
}
return received.OutputRoot == expected, nil
}
package challenger
import (
"context"
"errors"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func TestChallenger_ValidateOutput_RollupClientErrors(t *testing.T) {
output := eth.OutputResponse{
Version: supportedL2OutputVersion,
OutputRoot: eth.Bytes32{},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, true)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), output.OutputRoot)
require.False(t, valid)
require.Nil(t, received)
require.ErrorIs(t, err, mockOutputApiError)
}
func TestChallenger_ValidateOutput_ErrorsWithWrongVersion(t *testing.T) {
output := eth.OutputResponse{
Version: eth.Bytes32{0x01},
OutputRoot: eth.Bytes32{0x01},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), eth.Bytes32{})
require.False(t, valid)
require.Nil(t, received)
require.ErrorIs(t, err, ErrUnsupportedL2OOVersion)
}
func TestChallenger_ValidateOutput_ErrorsInvalidBlockNumber(t *testing.T) {
output := eth.OutputResponse{
Version: supportedL2OutputVersion,
OutputRoot: eth.Bytes32{0x01},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, received, err := challenger.ValidateOutput(context.Background(), big.NewInt(1), output.OutputRoot)
require.False(t, valid)
require.Nil(t, received)
require.ErrorIs(t, err, ErrInvalidBlockNumber)
}
func TestOutput_ValidateOutput(t *testing.T) {
output := eth.OutputResponse{
Version: eth.Bytes32{},
OutputRoot: eth.Bytes32{},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, expected, err := challenger.ValidateOutput(context.Background(), big.NewInt(0), output.OutputRoot)
require.Equal(t, *expected, output.OutputRoot)
require.True(t, valid)
require.NoError(t, err)
}
func TestChallenger_CompareOutputRoots_ErrorsWithDifferentRoots(t *testing.T) {
output := eth.OutputResponse{
Version: eth.Bytes32{0xFF, 0xFF, 0xFF, 0xFF},
OutputRoot: eth.Bytes32{},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(0))
require.False(t, valid)
require.ErrorIs(t, err, ErrUnsupportedL2OOVersion)
}
func TestChallenger_CompareOutputRoots_ErrInvalidBlockNumber(t *testing.T) {
output := eth.OutputResponse{
Version: supportedL2OutputVersion,
OutputRoot: eth.Bytes32{},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(1))
require.False(t, valid)
require.ErrorIs(t, err, ErrInvalidBlockNumber)
}
func TestChallenger_CompareOutputRoots_Succeeds(t *testing.T) {
output := eth.OutputResponse{
Version: supportedL2OutputVersion,
OutputRoot: eth.Bytes32{},
BlockRef: eth.L2BlockRef{},
}
challenger := newTestChallenger(t, output, false)
valid, err := challenger.compareOutputRoots(&output, output.OutputRoot, big.NewInt(0))
require.True(t, valid)
require.NoError(t, err)
valid, err = challenger.compareOutputRoots(&output, eth.Bytes32{0x01}, big.NewInt(0))
require.False(t, valid)
require.NoError(t, err)
}
func newTestChallenger(t *testing.T, output eth.OutputResponse, errors bool) *Challenger {
outputApi := newMockOutputApi(output, errors)
log := testlog.Logger(t, log.LvlError)
metr := metrics.NewMetrics("test")
challenger := Challenger{
rollupClient: outputApi,
log: log,
metr: metr,
networkTimeout: time.Duration(5) * time.Second,
}
return &challenger
}
var mockOutputApiError = errors.New("mock output api error")
type mockOutputApi struct {
mock.Mock
expected eth.OutputResponse
errors bool
}
func newMockOutputApi(output eth.OutputResponse, errors bool) *mockOutputApi {
return &mockOutputApi{
expected: output,
errors: errors,
}
}
func (m *mockOutputApi) OutputAtBlock(ctx context.Context, blockNumber uint64) (*eth.OutputResponse, error) {
if m.errors {
return nil, mockOutputApiError
}
return &m.expected, 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