Commit 85de3b31 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Send challenge transactions (#9243)

* op-challenger: Single threaded approach to verifying and sending challenges.

* op-challenger: Verify images using multiple threads.

* op-challenger: Add tests for logging errors.

* op-challenger: Remove todo
parent 94fc9d38
package keccak
import (
"context"
"errors"
"fmt"
"sync"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type Oracle interface {
fetcher.Oracle
ChallengeTx(ident keccakTypes.LargePreimageIdent, challenge keccakTypes.Challenge) (txmgr.TxCandidate, error)
}
type Verifier interface {
CreateChallenge(ctx context.Context, blockHash common.Hash, oracle fetcher.Oracle, preimage keccakTypes.LargePreimageMetaData) (keccakTypes.Challenge, error)
}
type Sender interface {
SendAndWait(txPurpose string, txs ...txmgr.TxCandidate) ([]*types.Receipt, error)
}
type PreimageChallenger struct {
log log.Logger
verifier Verifier
sender Sender
}
func NewPreimageChallenger(logger log.Logger, verifier Verifier, sender Sender) *PreimageChallenger {
return &PreimageChallenger{
log: logger,
verifier: verifier,
sender: sender,
}
}
func (c *PreimageChallenger) Challenge(ctx context.Context, blockHash common.Hash, oracle Oracle, preimages []keccakTypes.LargePreimageMetaData) error {
var txLock sync.Mutex
var wg sync.WaitGroup
var txs []txmgr.TxCandidate
for _, preimage := range preimages {
preimage := preimage
wg.Add(1)
go func() {
defer wg.Done()
logger := c.log.New("oracle", oracle.Addr(), "claimant", preimage.Claimant, "uuid", preimage.UUID)
challenge, err := c.verifier.CreateChallenge(ctx, blockHash, oracle, preimage)
if errors.Is(err, matrix.ErrValid) {
logger.Debug("Preimage is valid")
return
} else if err != nil {
logger.Error("Failed to verify large preimage", "err", err)
return
}
tx, err := oracle.ChallengeTx(preimage.LargePreimageIdent, challenge)
if err != nil {
logger.Error("Failed to create challenge transaction", "err", err)
return
}
txLock.Lock()
defer txLock.Unlock()
txs = append(txs, tx)
}()
}
wg.Wait()
if len(txs) > 0 {
_, err := c.sender.SendAndWait("challenge preimages", txs...)
if err != nil {
return fmt.Errorf("failed to send challenge txs: %w", err)
}
}
return nil
}
package keccak
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestChallenge(t *testing.T) {
preimages := []keccakTypes.LargePreimageMetaData{
{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xff, 0x00},
UUID: big.NewInt(0),
},
},
{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xff, 0x01},
UUID: big.NewInt(1),
},
},
{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xff, 0x02},
UUID: big.NewInt(2),
},
},
}
logger := testlog.Logger(t, log.LvlInfo)
t.Run("SendChallenges", func(t *testing.T) {
verifier, sender, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.challenges[preimages[2].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x02}}
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
// Should send the two challenges before returning
require.Len(t, sender.sent, 1, "Should send a single batch of transactions")
for ident, challenge := range verifier.challenges {
tx, err := oracle.ChallengeTx(ident, challenge)
require.NoError(t, err)
require.Contains(t, sender.sent[0], tx)
}
})
t.Run("ReturnErrorWhenSendingFails", func(t *testing.T) {
verifier, sender, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
sender.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.ErrorIs(t, err, sender.err)
})
t.Run("LogErrorWhenCreateTxFails", func(t *testing.T) {
logs := testlog.Capture(logger)
verifier, _, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
oracle.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
errLog := logs.FindLog(log.LvlError, "Failed to create challenge transaction")
require.ErrorIs(t, errLog.GetContextValue("err").(error), oracle.err)
})
t.Run("LogErrorWhenVerifierFails", func(t *testing.T) {
logs := testlog.Capture(logger)
verifier, _, oracle, challenger := setupChallengerTest(logger)
verifier.challenges[preimages[1].LargePreimageIdent] = keccakTypes.Challenge{StateMatrix: []byte{0x01}}
verifier.err = errors.New("boom")
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
errLog := logs.FindLog(log.LvlError, "Failed to verify large preimage")
require.ErrorIs(t, errLog.GetContextValue("err").(error), verifier.err)
})
t.Run("DoNotLogErrValid", func(t *testing.T) {
logs := testlog.Capture(logger)
_, _, oracle, challenger := setupChallengerTest(logger)
// All preimages are valid
err := challenger.Challenge(context.Background(), common.Hash{0xaa}, oracle, preimages)
require.NoError(t, err)
errLog := logs.FindLog(log.LvlError, "Failed to verify large preimage")
require.Nil(t, errLog)
dbgLog := logs.FindLog(log.LvlDebug, "Preimage is valid")
require.NotNil(t, dbgLog)
})
}
func setupChallengerTest(logger log.Logger) (*stubVerifier, *stubSender, *stubChallengerOracle, *PreimageChallenger) {
verifier := &stubVerifier{
challenges: make(map[keccakTypes.LargePreimageIdent]keccakTypes.Challenge),
}
sender := &stubSender{}
oracle := &stubChallengerOracle{}
challenger := NewPreimageChallenger(logger, verifier, sender)
return verifier, sender, oracle, challenger
}
type stubVerifier struct {
challenges map[keccakTypes.LargePreimageIdent]keccakTypes.Challenge
err error
}
func (s *stubVerifier) CreateChallenge(_ context.Context, _ common.Hash, _ fetcher.Oracle, preimage keccakTypes.LargePreimageMetaData) (keccakTypes.Challenge, error) {
if s.err != nil {
return keccakTypes.Challenge{}, s.err
}
challenge, ok := s.challenges[preimage.LargePreimageIdent]
if !ok {
return keccakTypes.Challenge{}, matrix.ErrValid
}
return challenge, nil
}
type stubSender struct {
err error
sent [][]txmgr.TxCandidate
}
func (s *stubSender) SendAndWait(_ string, txs ...txmgr.TxCandidate) ([]*types.Receipt, error) {
if s.err != nil {
return nil, s.err
}
s.sent = append(s.sent, txs)
return nil, nil
}
type stubChallengerOracle struct {
stubOracle
err error
}
func (s *stubChallengerOracle) ChallengeTx(ident keccakTypes.LargePreimageIdent, challenge keccakTypes.Challenge) (txmgr.TxCandidate, error) {
if s.err != nil {
return txmgr.TxCandidate{}, s.err
}
return txmgr.TxCandidate{
To: &ident.Claimant,
TxData: append(ident.UUID.Bytes(), challenge.StateMatrix...),
}, nil
}
......@@ -4,31 +4,30 @@ import (
"context"
"sync"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
type Verifier interface {
Verify(ctx context.Context, blockHash common.Hash, oracle fetcher.Oracle, preimage keccakTypes.LargePreimageMetaData) error
type Challenger interface {
Challenge(ctx context.Context, blockHash common.Hash, oracle Oracle, preimages []keccakTypes.LargePreimageMetaData) error
}
type LargePreimageScheduler struct {
log log.Logger
ch chan common.Hash
oracles []keccakTypes.LargePreimageOracle
verifier Verifier
challenger Challenger
cancel func()
wg sync.WaitGroup
}
func NewLargePreimageScheduler(logger log.Logger, oracles []keccakTypes.LargePreimageOracle, verifier Verifier) *LargePreimageScheduler {
func NewLargePreimageScheduler(logger log.Logger, oracles []keccakTypes.LargePreimageOracle, challenger Challenger) *LargePreimageScheduler {
return &LargePreimageScheduler{
log: logger,
ch: make(chan common.Hash, 1),
oracles: oracles,
verifier: verifier,
challenger: challenger,
}
}
......@@ -79,12 +78,14 @@ func (s *LargePreimageScheduler) verifyPreimages(ctx context.Context, blockHash
func (s *LargePreimageScheduler) verifyOraclePreimages(ctx context.Context, oracle keccakTypes.LargePreimageOracle, blockHash common.Hash) error {
preimages, err := oracle.GetActivePreimages(ctx, blockHash)
if err != nil {
return err
}
toVerify := make([]keccakTypes.LargePreimageMetaData, 0, len(preimages))
for _, preimage := range preimages {
if preimage.ShouldVerify() {
if err := s.verifier.Verify(ctx, blockHash, oracle, preimage); err != nil {
s.log.Error("Failed to verify large preimage", "oracle", oracle.Addr(), "claimant", preimage.Claimant, "uuid", preimage.UUID, "err", err)
toVerify = append(toVerify, preimage)
}
}
}
return err
return s.challenger.Challenge(ctx, blockHash, oracle, toVerify)
}
......@@ -7,10 +7,10 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
......@@ -43,8 +43,8 @@ func TestScheduleNextCheck(t *testing.T) {
oracle := &stubOracle{
images: []keccakTypes.LargePreimageMetaData{preimage1, preimage2, preimage3},
}
verifier := &stubVerifier{}
scheduler := NewLargePreimageScheduler(logger, []keccakTypes.LargePreimageOracle{oracle}, verifier)
challenger := &stubChallenger{}
scheduler := NewLargePreimageScheduler(logger, []keccakTypes.LargePreimageOracle{oracle}, challenger)
scheduler.Start(ctx)
defer scheduler.Close()
err := scheduler.Schedule(common.Hash{0xaa}, 3)
......@@ -53,8 +53,8 @@ func TestScheduleNextCheck(t *testing.T) {
return oracle.GetPreimagesCount() == 1
}, 10*time.Second, 10*time.Millisecond)
require.Eventually(t, func() bool {
verified := verifier.Verified()
t.Logf("Verified preimages: %v", verified)
verified := challenger.Checked()
t.Logf("Checked preimages: %v", verified)
return len(verified) == 1 && verified[0] == preimage3
}, 10*time.Second, 10*time.Millisecond, "Did not verify preimage")
}
......@@ -91,22 +91,26 @@ func (s *stubOracle) GetPreimagesCount() int {
return s.getPreimagesCount
}
type stubVerifier struct {
func (s *stubOracle) ChallengeTx(_ keccakTypes.LargePreimageIdent, _ keccakTypes.Challenge) (txmgr.TxCandidate, error) {
panic("not supported")
}
type stubChallenger struct {
m sync.Mutex
verified []keccakTypes.LargePreimageMetaData
checked []keccakTypes.LargePreimageMetaData
}
func (s *stubVerifier) Verify(_ context.Context, _ common.Hash, _ fetcher.Oracle, image keccakTypes.LargePreimageMetaData) error {
func (s *stubChallenger) Challenge(_ context.Context, _ common.Hash, _ Oracle, preimages []keccakTypes.LargePreimageMetaData) error {
s.m.Lock()
defer s.m.Unlock()
s.verified = append(s.verified, image)
s.checked = append(s.checked, preimages...)
return nil
}
func (s *stubVerifier) Verified() []keccakTypes.LargePreimageMetaData {
func (s *stubChallenger) Checked() []keccakTypes.LargePreimageMetaData {
s.m.Lock()
defer s.m.Unlock()
v := make([]keccakTypes.LargePreimageMetaData, len(s.verified))
copy(v, s.verified)
v := make([]keccakTypes.LargePreimageMetaData, len(s.checked))
copy(v, s.checked)
return v
}
......@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/merkle"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
......@@ -87,4 +88,5 @@ type LargePreimageOracle interface {
GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]LargePreimageMetaData, error)
GetInputDataBlocks(ctx context.Context, block batching.Block, ident LargePreimageIdent) ([]uint64, error)
DecodeInputData(data []byte) (*big.Int, InputData, error)
ChallengeTx(ident LargePreimageIdent, challenge Challenge) (txmgr.TxCandidate, error)
}
......@@ -32,10 +32,10 @@ func NewPreimageVerifier(logger log.Logger, fetcher Fetcher) *PreimageVerifier {
}
}
func (v *PreimageVerifier) Verify(ctx context.Context, blockHash common.Hash, oracle fetcher.Oracle, preimage keccakTypes.LargePreimageMetaData) error {
func (v *PreimageVerifier) CreateChallenge(ctx context.Context, blockHash common.Hash, oracle fetcher.Oracle, preimage keccakTypes.LargePreimageMetaData) (keccakTypes.Challenge, error) {
inputs, err := v.fetcher.FetchInputs(ctx, blockHash, oracle, preimage.LargePreimageIdent)
if err != nil {
return fmt.Errorf("failed to fetch leaves: %w", err)
return keccakTypes.Challenge{}, fmt.Errorf("failed to fetch leaves: %w", err)
}
readers := make([]io.Reader, 0, len(inputs))
var commitments []common.Hash
......@@ -43,12 +43,9 @@ func (v *PreimageVerifier) Verify(ctx context.Context, blockHash common.Hash, or
readers = append(readers, bytes.NewReader(input.Input))
commitments = append(commitments, input.Commitments...)
}
_, err = matrix.Challenge(io.MultiReader(readers...), commitments)
if errors.Is(err, matrix.ErrValid) {
return nil
} else if err != nil {
return fmt.Errorf("failed to verify preimage: %w", err)
challenge, err := matrix.Challenge(io.MultiReader(readers...), commitments)
if err != nil {
return keccakTypes.Challenge{}, fmt.Errorf("failed to create challenge: %w", err)
}
// TODO(client-pod#480): Implement sending the challenge transaction
return ErrNotImplemented
return challenge, nil
}
......@@ -28,10 +28,12 @@ func TestVerify(t *testing.T) {
{
name: "Valid-SingleInput",
inputs: func() []keccakTypes.InputData { return validInputs(t, 1) },
expectedErr: matrix.ErrValid,
},
{
name: "Valid-MultipleInputs",
inputs: func() []keccakTypes.InputData { return validInputs(t, 3) },
expectedErr: matrix.ErrValid,
},
{
name: "Invalid-FirstCommitment",
......@@ -40,7 +42,7 @@ func TestVerify(t *testing.T) {
inputs[0].Commitments[0] = common.Hash{0xaa}
return inputs
},
expectedErr: ErrNotImplemented,
expectedErr: nil,
},
{
name: "Invalid-MiddleCommitment",
......@@ -49,7 +51,7 @@ func TestVerify(t *testing.T) {
inputs[0].Commitments[1] = common.Hash{0xaa}
return inputs
},
expectedErr: ErrNotImplemented,
expectedErr: nil,
},
{
name: "Invalid-LastCommitment",
......@@ -58,7 +60,7 @@ func TestVerify(t *testing.T) {
inputs[2].Commitments[len(inputs[2].Commitments)-1] = common.Hash{0xaa}
return inputs
},
expectedErr: ErrNotImplemented,
expectedErr: nil,
},
}
......@@ -70,8 +72,15 @@ func TestVerify(t *testing.T) {
}
verifier := NewPreimageVerifier(logger, fetcher)
preimage := keccakTypes.LargePreimageMetaData{}
err := verifier.Verify(context.Background(), common.Hash{0xff}, &stubOracle{}, preimage)
challenge, err := verifier.CreateChallenge(context.Background(), common.Hash{0xff}, &stubOracle{}, preimage)
require.ErrorIs(t, err, test.expectedErr)
if err == nil {
// Leave checking the validity of the challenge to the StateMatrix tests
// Just confirm that we got a non-zero challenge
require.NotEqual(t, keccakTypes.Challenge{}, challenge)
} else {
require.Equal(t, keccakTypes.Challenge{}, challenge)
}
})
}
}
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
......@@ -62,6 +63,10 @@ func TestDeduplicateOracles(t *testing.T) {
type stubPreimageOracle common.Address
func (s stubPreimageOracle) ChallengeTx(_ keccakTypes.LargePreimageIdent, _ keccakTypes.Challenge) (txmgr.TxCandidate, error) {
panic("not supported")
}
func (s stubPreimageOracle) GetInputDataBlocks(_ context.Context, _ batching.Block, _ keccakTypes.LargePreimageIdent) ([]uint64, error) {
panic("not supported")
}
......
......@@ -234,7 +234,8 @@ func (s *Service) initScheduler(cfg *config.Config) error {
func (s *Service) initLargePreimages() error {
fetcher := fetcher.NewPreimageFetcher(s.logger, s.l1Client)
verifier := keccak.NewPreimageVerifier(s.logger, fetcher)
s.preimages = keccak.NewLargePreimageScheduler(s.logger, s.registry.Oracles(), verifier)
challenger := keccak.NewPreimageChallenger(s.logger, verifier, s.txSender)
s.preimages = keccak.NewLargePreimageScheduler(s.logger, s.registry.Oracles(), challenger)
return 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