Commit 41c2d871 authored by refcell.eth's avatar refcell.eth Committed by GitHub

feat(op-challenger): refactor preimage uploader in responder. (#9017)

parent cb948687
package preimages
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
var _ PreimageUploader = (*DirectPreimageUploader)(nil)
var ErrNilPreimageData = fmt.Errorf("cannot upload nil preimage data")
type PreimageOracleContract interface {
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}
// DirectPreimageUploader uploads the provided [types.PreimageOracleData]
// directly to the PreimageOracle contract in a single transaction.
type DirectPreimageUploader struct {
log log.Logger
txMgr txmgr.TxManager
contract PreimageOracleContract
}
func NewDirectPreimageUploader(logger log.Logger, txMgr txmgr.TxManager, contract PreimageOracleContract) *DirectPreimageUploader {
return &DirectPreimageUploader{logger, txMgr, contract}
}
func (d *DirectPreimageUploader) UploadPreimage(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) error {
if data == nil {
return ErrNilPreimageData
}
d.log.Info("Updating oracle data", "key", data.OracleKey)
candidate, err := d.contract.UpdateOracleTx(ctx, claimIdx, data)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
}
if err := d.sendTxAndWait(ctx, candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err)
}
return nil
}
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (d *DirectPreimageUploader) sendTxAndWait(ctx context.Context, candidate txmgr.TxCandidate) error {
receipt, err := d.txMgr.Send(ctx, candidate)
if err != nil {
return err
}
if receipt.Status == ethtypes.ReceiptStatusFailed {
d.log.Error("DirectPreimageUploader tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else {
d.log.Debug("DirectPreimageUploader tx successfully published", "tx_hash", receipt.TxHash)
}
return nil
}
package preimages
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
mockUpdateOracleTxError = errors.New("mock update oracle tx error")
mockTxMgrSendError = errors.New("mock tx mgr send error")
)
func TestDirectPreimageUploader_UploadPreimage(t *testing.T) {
t.Run("UpdateOracleTxFails", func(t *testing.T) {
oracle, txMgr, contract := newTestDirectPreimageUploader(t)
contract.uploadFails = true
err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{})
require.ErrorIs(t, err, mockUpdateOracleTxError)
require.Equal(t, 1, contract.updates)
require.Equal(t, 0, txMgr.sends) // verify that the tx was not sent
})
t.Run("SendFails", func(t *testing.T) {
oracle, txMgr, contract := newTestDirectPreimageUploader(t)
txMgr.sendFails = true
err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{})
require.ErrorIs(t, err, mockTxMgrSendError)
require.Equal(t, 1, contract.updates)
require.Equal(t, 1, txMgr.sends)
})
t.Run("NilPreimageData", func(t *testing.T) {
oracle, _, _ := newTestDirectPreimageUploader(t)
err := oracle.UploadPreimage(context.Background(), 0, nil)
require.ErrorIs(t, err, ErrNilPreimageData)
})
t.Run("Success", func(t *testing.T) {
oracle, _, contract := newTestDirectPreimageUploader(t)
err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{})
require.NoError(t, err)
require.Equal(t, 1, contract.updates)
})
}
func TestDirectPreimageUploader_SendTxAndWait(t *testing.T) {
t.Run("SendFails", func(t *testing.T) {
oracle, txMgr, _ := newTestDirectPreimageUploader(t)
txMgr.sendFails = true
err := oracle.sendTxAndWait(context.Background(), txmgr.TxCandidate{})
require.ErrorIs(t, err, mockTxMgrSendError)
require.Equal(t, 1, txMgr.sends)
})
t.Run("ReceiptStatusFailed", func(t *testing.T) {
oracle, txMgr, _ := newTestDirectPreimageUploader(t)
txMgr.statusFail = true
err := oracle.sendTxAndWait(context.Background(), txmgr.TxCandidate{})
require.NoError(t, err)
require.Equal(t, 1, txMgr.sends)
})
t.Run("Success", func(t *testing.T) {
oracle, txMgr, _ := newTestDirectPreimageUploader(t)
err := oracle.sendTxAndWait(context.Background(), txmgr.TxCandidate{})
require.NoError(t, err)
require.Equal(t, 1, txMgr.sends)
})
}
func newTestDirectPreimageUploader(t *testing.T) (*DirectPreimageUploader, *mockTxMgr, *mockPreimageOracleContract) {
logger := testlog.Logger(t, log.LvlError)
txMgr := &mockTxMgr{}
contract := &mockPreimageOracleContract{}
return NewDirectPreimageUploader(logger, txMgr, contract), txMgr, contract
}
type mockPreimageOracleContract struct {
updates int
uploadFails bool
}
func (s *mockPreimageOracleContract) UpdateOracleTx(_ context.Context, _ uint64, _ *types.PreimageOracleData) (txmgr.TxCandidate, error) {
s.updates++
if s.uploadFails {
return txmgr.TxCandidate{}, mockUpdateOracleTxError
}
return txmgr.TxCandidate{}, nil
}
type mockTxMgr struct {
sends int
sendFails bool
statusFail bool
}
func (s *mockTxMgr) Send(_ context.Context, _ txmgr.TxCandidate) (*ethtypes.Receipt, error) {
s.sends++
if s.sendFails {
return nil, mockTxMgrSendError
}
if s.statusFail {
return &ethtypes.Receipt{Status: ethtypes.ReceiptStatusFailed}, nil
}
return &ethtypes.Receipt{}, nil
}
func (s *mockTxMgr) BlockNumber(_ context.Context) (uint64, error) { return 0, nil }
func (s *mockTxMgr) From() common.Address { return common.Address{} }
func (s *mockTxMgr) Close() {}
package preimages
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
)
// PreimageUploader is responsible for posting preimages.
type PreimageUploader interface {
// UploadPreimage uploads the provided preimage.
UploadPreimage(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) error
}
......@@ -5,6 +5,7 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
......@@ -32,6 +33,7 @@ type FaultResponder struct {
txMgr txmgr.TxManager
contract GameContract
uploader preimages.PreimageUploader
}
// NewFaultResponder returns a new [FaultResponder].
......@@ -40,6 +42,7 @@ func NewFaultResponder(logger log.Logger, txMgr txmgr.TxManager, contract GameCo
log: logger,
txMgr: txMgr,
contract: contract,
uploader: preimages.NewDirectPreimageUploader(logger, txMgr, contract),
}, nil
}
......@@ -76,13 +79,9 @@ func (r *FaultResponder) ResolveClaim(ctx context.Context, claimIdx uint64) erro
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
if action.OracleData != nil {
r.log.Info("Updating oracle data", "key", action.OracleData.OracleKey)
candidate, err := r.contract.UpdateOracleTx(ctx, uint64(action.ParentIdx), action.OracleData)
err := r.uploader.UploadPreimage(ctx, uint64(action.ParentIdx), action.OracleData)
if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
}
if err := r.sendTxAndWait(ctx, candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err)
return fmt.Errorf("failed to upload preimage: %w", err)
}
}
var candidate txmgr.TxCandidate
......
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