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

op-challenger: Tidy up TxSender (#9931)

Don't return receipts as they always ignored
Return an error when a transaction publishes but reverts rather than logging and ignoring
Add a method that returns errors for each individual transaction
parent 4af4a302
...@@ -12,6 +12,10 @@ import ( ...@@ -12,6 +12,10 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type TxSender interface {
SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error
}
type BondClaimMetrics interface { type BondClaimMetrics interface {
RecordBondClaimed(amount uint64) RecordBondClaimed(amount uint64)
} }
...@@ -27,13 +31,13 @@ type Claimer struct { ...@@ -27,13 +31,13 @@ type Claimer struct {
logger log.Logger logger log.Logger
metrics BondClaimMetrics metrics BondClaimMetrics
contractCreator BondContractCreator contractCreator BondContractCreator
txSender types.TxSender txSender TxSender
claimants []common.Address claimants []common.Address
} }
var _ BondClaimer = (*Claimer)(nil) var _ BondClaimer = (*Claimer)(nil)
func NewBondClaimer(l log.Logger, m BondClaimMetrics, contractCreator BondContractCreator, txSender types.TxSender, claimants ...common.Address) *Claimer { func NewBondClaimer(l log.Logger, m BondClaimMetrics, contractCreator BondContractCreator, txSender TxSender, claimants ...common.Address) *Claimer {
return &Claimer{ return &Claimer{
logger: l, logger: l,
metrics: m, metrics: m,
...@@ -78,7 +82,7 @@ func (c *Claimer) claimBond(ctx context.Context, game types.GameMetadata, addr c ...@@ -78,7 +82,7 @@ func (c *Claimer) claimBond(ctx context.Context, game types.GameMetadata, addr c
return fmt.Errorf("failed to create credit claim tx: %w", err) return fmt.Errorf("failed to create credit claim tx: %w", err)
} }
if _, err = c.txSender.SendAndWait("claim credit", candidate); err != nil { if err = c.txSender.SendAndWaitSimple("claim credit", candidate); err != nil {
return fmt.Errorf("failed to claim credit: %w", err) return fmt.Errorf("failed to claim credit: %w", err)
} }
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -132,15 +131,15 @@ func (s *mockTxSender) From() common.Address { ...@@ -132,15 +131,15 @@ func (s *mockTxSender) From() common.Address {
return common.HexToAddress("0x33333") return common.HexToAddress("0x33333")
} }
func (s *mockTxSender) SendAndWait(_ string, _ ...txmgr.TxCandidate) ([]*ethtypes.Receipt, error) { func (s *mockTxSender) SendAndWaitSimple(_ string, _ ...txmgr.TxCandidate) error {
s.sends++ s.sends++
if s.sendFails { if s.sendFails {
return nil, mockTxMgrSendError return mockTxMgrSendError
} }
if s.statusFail { if s.statusFail {
return []*ethtypes.Receipt{{Status: ethtypes.ReceiptStatusFailed}}, nil return errors.New("transaction reverted")
} }
return []*ethtypes.Receipt{{Status: ethtypes.ReceiptStatusSuccessful}}, nil return nil
} }
type stubBondContract struct { type stubBondContract struct {
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types" gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -34,6 +35,11 @@ type L1HeaderSource interface { ...@@ -34,6 +35,11 @@ type L1HeaderSource interface {
HeaderByHash(context.Context, common.Hash) (*gethTypes.Header, error) HeaderByHash(context.Context, common.Hash) (*gethTypes.Header, error)
} }
type TxSender interface {
From() common.Address
SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error
}
type GamePlayer struct { type GamePlayer struct {
act actor act actor
loader GameInfo loader GameInfo
...@@ -66,7 +72,7 @@ func NewGamePlayer( ...@@ -66,7 +72,7 @@ func NewGamePlayer(
m metrics.Metricer, m metrics.Metricer,
dir string, dir string,
addr common.Address, addr common.Address,
txSender gameTypes.TxSender, txSender TxSender,
loader GameContract, loader GameContract,
syncValidator SyncValidator, syncValidator SyncValidator,
validators []Validator, validators []Validator,
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "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" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -21,11 +20,11 @@ type PreimageGameContract interface { ...@@ -21,11 +20,11 @@ type PreimageGameContract interface {
type DirectPreimageUploader struct { type DirectPreimageUploader struct {
log log.Logger log log.Logger
txSender gameTypes.TxSender txSender TxSender
contract PreimageGameContract contract PreimageGameContract
} }
func NewDirectPreimageUploader(logger log.Logger, txSender gameTypes.TxSender, contract PreimageGameContract) *DirectPreimageUploader { func NewDirectPreimageUploader(logger log.Logger, txSender TxSender, contract PreimageGameContract) *DirectPreimageUploader {
return &DirectPreimageUploader{logger, txSender, contract} return &DirectPreimageUploader{logger, txSender, contract}
} }
...@@ -38,7 +37,7 @@ func (d *DirectPreimageUploader) UploadPreimage(ctx context.Context, claimIdx ui ...@@ -38,7 +37,7 @@ func (d *DirectPreimageUploader) UploadPreimage(ctx context.Context, claimIdx ui
if err != nil { if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err) return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
} }
if _, err := d.txSender.SendAndWait("populate pre-image oracle", candidate); err != nil { if err := d.txSender.SendAndWaitSimple("populate pre-image oracle", candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err) return fmt.Errorf("failed to populate pre-image oracle: %w", err)
} }
return nil return nil
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -83,13 +82,13 @@ func (s *mockTxSender) From() common.Address { ...@@ -83,13 +82,13 @@ func (s *mockTxSender) From() common.Address {
return common.Address{} return common.Address{}
} }
func (s *mockTxSender) SendAndWait(_ string, _ ...txmgr.TxCandidate) ([]*ethtypes.Receipt, error) { func (s *mockTxSender) SendAndWaitSimple(_ string, _ ...txmgr.TxCandidate) error {
s.sends++ s.sends++
if s.sendFails { if s.sendFails {
return nil, mockTxMgrSendError return mockTxMgrSendError
} }
if s.statusFail { if s.statusFail {
return []*ethtypes.Receipt{{Status: ethtypes.ReceiptStatusFailed}}, nil return errors.New("transaction reverted")
} }
return []*ethtypes.Receipt{{Status: ethtypes.ReceiptStatusSuccessful}}, nil return nil
} }
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -41,11 +40,11 @@ type LargePreimageUploader struct { ...@@ -41,11 +40,11 @@ type LargePreimageUploader struct {
log log.Logger log log.Logger
clock types.ClockReader clock types.ClockReader
txSender gameTypes.TxSender txSender TxSender
contract PreimageOracleContract contract PreimageOracleContract
} }
func NewLargePreimageUploader(logger log.Logger, cl types.ClockReader, txSender gameTypes.TxSender, contract PreimageOracleContract) *LargePreimageUploader { func NewLargePreimageUploader(logger log.Logger, cl types.ClockReader, txSender TxSender, contract PreimageOracleContract) *LargePreimageUploader {
return &LargePreimageUploader{logger, cl, txSender, contract} return &LargePreimageUploader{logger, cl, txSender, contract}
} }
...@@ -151,7 +150,7 @@ func (p *LargePreimageUploader) Squeeze(ctx context.Context, uuid *big.Int, stat ...@@ -151,7 +150,7 @@ func (p *LargePreimageUploader) Squeeze(ctx context.Context, uuid *big.Int, stat
if err != nil { if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err) return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
} }
if _, err := p.txSender.SendAndWait("squeeze large preimage", tx); err != nil { if err := p.txSender.SendAndWaitSimple("squeeze large preimage", tx); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err) return fmt.Errorf("failed to populate pre-image oracle: %w", err)
} }
return nil return nil
...@@ -170,7 +169,7 @@ func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint ...@@ -170,7 +169,7 @@ func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint
return fmt.Errorf("failed to get min bond for large preimage proposal: %w", err) return fmt.Errorf("failed to get min bond for large preimage proposal: %w", err)
} }
candidate.Value = bond candidate.Value = bond
if _, err := p.txSender.SendAndWait("init large preimage", candidate); err != nil { if err := p.txSender.SendAndWaitSimple("init large preimage", candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err) return fmt.Errorf("failed to populate pre-image oracle: %w", err)
} }
return nil return nil
...@@ -191,6 +190,5 @@ func (p *LargePreimageUploader) addLargePreimageData(uuid *big.Int, chunks []kec ...@@ -191,6 +190,5 @@ func (p *LargePreimageUploader) addLargePreimageData(uuid *big.Int, chunks []kec
txs[i] = tx txs[i] = tx
} }
p.log.Info("Adding large preimage leaves", "uuid", uuid, "blocksProcessed", blocksProcessed, "txs", len(txs)) p.log.Info("Adding large preimage leaves", "uuid", uuid, "blocksProcessed", blocksProcessed, "txs", len(txs))
_, err := p.txSender.SendAndWait("add leaf to large preimage", txs...) return p.txSender.SendAndWaitSimple("add leaf to large preimage", txs...)
return err
} }
...@@ -21,6 +21,11 @@ type PreimageUploader interface { ...@@ -21,6 +21,11 @@ type PreimageUploader interface {
UploadPreimage(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) error UploadPreimage(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) error
} }
type TxSender interface {
From() common.Address
SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error
}
// PreimageOracleContract is the interface for interacting with the PreimageOracle contract. // PreimageOracleContract is the interface for interacting with the PreimageOracle contract.
type PreimageOracleContract interface { type PreimageOracleContract interface {
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
......
...@@ -49,7 +49,7 @@ func RegisterGameTypes( ...@@ -49,7 +49,7 @@ func RegisterGameTypes(
registry Registry, registry Registry,
oracles OracleRegistry, oracles OracleRegistry,
rollupClient RollupClient, rollupClient RollupClient,
txSender types.TxSender, txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract, gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
l1HeaderSource L1HeaderSource, l1HeaderSource L1HeaderSource,
...@@ -96,7 +96,7 @@ func registerAlphabet( ...@@ -96,7 +96,7 @@ func registerAlphabet(
m metrics.Metricer, m metrics.Metricer,
syncValidator SyncValidator, syncValidator SyncValidator,
rollupClient RollupClient, rollupClient RollupClient,
txSender types.TxSender, txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract, gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
l1HeaderSource L1HeaderSource, l1HeaderSource L1HeaderSource,
...@@ -179,7 +179,7 @@ func registerCannon( ...@@ -179,7 +179,7 @@ func registerCannon(
cfg *config.Config, cfg *config.Config,
syncValidator SyncValidator, syncValidator SyncValidator,
rollupClient outputs.OutputRollupClient, rollupClient outputs.OutputRollupClient,
txSender types.TxSender, txSender TxSender,
gameFactory *contracts.DisputeGameFactoryContract, gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
l2Client cannon.L2HeaderSource, l2Client cannon.L2HeaderSource,
......
...@@ -30,17 +30,21 @@ type Oracle interface { ...@@ -30,17 +30,21 @@ type Oracle interface {
GlobalDataExists(ctx context.Context, data *types.PreimageOracleData) (bool, error) GlobalDataExists(ctx context.Context, data *types.PreimageOracleData) (bool, error)
} }
type TxSender interface {
SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error
}
// FaultResponder implements the [Responder] interface to send onchain transactions. // FaultResponder implements the [Responder] interface to send onchain transactions.
type FaultResponder struct { type FaultResponder struct {
log log.Logger log log.Logger
sender gameTypes.TxSender sender TxSender
contract GameContract contract GameContract
uploader preimages.PreimageUploader uploader preimages.PreimageUploader
oracle Oracle oracle Oracle
} }
// NewFaultResponder returns a new [FaultResponder]. // NewFaultResponder returns a new [FaultResponder].
func NewFaultResponder(logger log.Logger, sender gameTypes.TxSender, contract GameContract, uploader preimages.PreimageUploader, oracle Oracle) (*FaultResponder, error) { func NewFaultResponder(logger log.Logger, sender TxSender, contract GameContract, uploader preimages.PreimageUploader, oracle Oracle) (*FaultResponder, error) {
return &FaultResponder{ return &FaultResponder{
log: logger, log: logger,
sender: sender, sender: sender,
...@@ -63,7 +67,7 @@ func (r *FaultResponder) Resolve() error { ...@@ -63,7 +67,7 @@ func (r *FaultResponder) Resolve() error {
return err return err
} }
return r.sendTxAndWait("resolve game", candidate) return r.sender.SendAndWaitSimple("resolve game", candidate)
} }
// CallResolveClaim determines if the resolveClaim function on the fault dispute game contract // CallResolveClaim determines if the resolveClaim function on the fault dispute game contract
...@@ -78,7 +82,7 @@ func (r *FaultResponder) ResolveClaim(claimIdx uint64) error { ...@@ -78,7 +82,7 @@ func (r *FaultResponder) ResolveClaim(claimIdx uint64) error {
if err != nil { if err != nil {
return err return err
} }
return r.sendTxAndWait("resolve claim", candidate) return r.sender.SendAndWaitSimple("resolve claim", candidate)
} }
func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error { func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) error {
...@@ -126,12 +130,5 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) ...@@ -126,12 +130,5 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action)
if err != nil { if err != nil {
return err return err
} }
return r.sendTxAndWait("perform action", candidate) return r.sender.SendAndWaitSimple("perform action", candidate)
}
// 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 (r *FaultResponder) sendTxAndWait(purpose string, candidate txmgr.TxCandidate) error {
_, err := r.sender.SendAndWait(purpose, candidate)
return err
} }
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -325,21 +324,15 @@ type mockTxManager struct { ...@@ -325,21 +324,15 @@ type mockTxManager struct {
sendFails bool sendFails bool
} }
func (m *mockTxManager) SendAndWait(_ string, txs ...txmgr.TxCandidate) ([]*ethtypes.Receipt, error) { func (m *mockTxManager) SendAndWaitSimple(_ string, txs ...txmgr.TxCandidate) error {
rcpts := make([]*ethtypes.Receipt, 0, len(txs))
for _, tx := range txs { for _, tx := range txs {
if m.sendFails { if m.sendFails {
return nil, mockSendError return mockSendError
} }
m.sends++ m.sends++
m.sent = append(m.sent, tx) m.sent = append(m.sent, tx)
rcpts = append(rcpts, ethtypes.NewReceipt(
[]byte{},
false,
0,
))
} }
return rcpts, nil return nil
} }
func (m *mockTxManager) BlockNumber(_ context.Context) (uint64, error) { func (m *mockTxManager) BlockNumber(_ context.Context) (uint64, error) {
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -29,7 +28,7 @@ type Verifier interface { ...@@ -29,7 +28,7 @@ type Verifier interface {
} }
type Sender interface { type Sender interface {
SendAndWait(txPurpose string, txs ...txmgr.TxCandidate) ([]*types.Receipt, error) SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error
} }
type PreimageChallenger struct { type PreimageChallenger struct {
...@@ -80,7 +79,7 @@ func (c *PreimageChallenger) Challenge(ctx context.Context, blockHash common.Has ...@@ -80,7 +79,7 @@ func (c *PreimageChallenger) Challenge(ctx context.Context, blockHash common.Has
wg.Wait() wg.Wait()
c.log.Debug("Created preimage challenge transactions", "count", len(txs)) c.log.Debug("Created preimage challenge transactions", "count", len(txs))
if len(txs) > 0 { if len(txs) > 0 {
_, err := c.sender.SendAndWait("challenge preimages", txs...) err := c.sender.SendAndWaitSimple("challenge preimages", txs...)
if err != nil { if err != nil {
c.metrics.RecordPreimageChallengeFailed() c.metrics.RecordPreimageChallengeFailed()
return fmt.Errorf("failed to send challenge txs: %w", err) return fmt.Errorf("failed to send challenge txs: %w", err)
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -151,12 +150,12 @@ type stubSender struct { ...@@ -151,12 +150,12 @@ type stubSender struct {
sent [][]txmgr.TxCandidate sent [][]txmgr.TxCandidate
} }
func (s *stubSender) SendAndWait(_ string, txs ...txmgr.TxCandidate) ([]*types.Receipt, error) { func (s *stubSender) SendAndWaitSimple(_ string, txs ...txmgr.TxCandidate) error {
if s.err != nil { if s.err != nil {
return nil, s.err return s.err
} }
s.sent = append(s.sent, txs) s.sent = append(s.sent, txs)
return nil, nil return nil
} }
type stubChallengerOracle struct { type stubChallengerOracle struct {
......
...@@ -4,9 +4,7 @@ import ( ...@@ -4,9 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
var ErrInvalidPrestate = errors.New("absolute prestate does not match") var ErrInvalidPrestate = errors.New("absolute prestate does not match")
...@@ -46,8 +44,3 @@ type GameMetadata struct { ...@@ -46,8 +44,3 @@ type GameMetadata struct {
Timestamp uint64 Timestamp uint64
Proxy common.Address Proxy common.Address
} }
type TxSender interface {
From() common.Address
SendAndWait(txPurpose string, txs ...txmgr.TxCandidate) ([]*ethtypes.Receipt, error)
}
...@@ -3,6 +3,7 @@ package sender ...@@ -3,6 +3,7 @@ package sender
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -10,6 +11,8 @@ import ( ...@@ -10,6 +11,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
var ErrTransactionReverted = errors.New("transaction published but reverted")
type TxSender struct { type TxSender struct {
log log.Logger log log.Logger
...@@ -30,27 +33,30 @@ func (s *TxSender) From() common.Address { ...@@ -30,27 +33,30 @@ func (s *TxSender) From() common.Address {
return s.txMgr.From() return s.txMgr.From()
} }
func (s *TxSender) SendAndWait(txPurpose string, txs ...txmgr.TxCandidate) ([]*types.Receipt, error) { func (s *TxSender) SendAndWaitDetailed(txPurpose string, txs ...txmgr.TxCandidate) []error {
receiptsCh := make(chan txmgr.TxReceipt[int], len(txs)) receiptsCh := make(chan txmgr.TxReceipt[int], len(txs))
for i, tx := range txs { for i, tx := range txs {
s.queue.Send(i, tx, receiptsCh) s.queue.Send(i, tx, receiptsCh)
} }
receipts := make([]*types.Receipt, len(txs))
completed := 0 completed := 0
var errs []error errs := make([]error, len(txs))
for completed < len(txs) { for completed < len(txs) {
rcpt := <-receiptsCh rcpt := <-receiptsCh
receipts[rcpt.ID] = rcpt.Receipt
completed++ completed++
if rcpt.Err != nil { if rcpt.Err != nil {
errs = append(errs, rcpt.Err) errs[rcpt.ID] = rcpt.Err
} else if rcpt.Receipt != nil { } else if rcpt.Receipt != nil {
if rcpt.Receipt.Status != types.ReceiptStatusSuccessful { if rcpt.Receipt.Status != types.ReceiptStatusSuccessful {
s.log.Error("Transaction published but reverted", "tx_hash", rcpt.Receipt.TxHash, "purpose", txPurpose) errs[rcpt.ID] = fmt.Errorf("%w purpose: %v hash: %v", ErrTransactionReverted, txPurpose, rcpt.Receipt.TxHash)
} else { } else {
s.log.Debug("Transaction successfully published", "tx_hash", rcpt.Receipt.TxHash, "purpose", txPurpose) s.log.Debug("Transaction successfully published", "tx_hash", rcpt.Receipt.TxHash, "purpose", txPurpose)
} }
} }
} }
return receipts, errors.Join(errs...) return errs
}
func (s *TxSender) SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error {
errs := s.SendAndWaitDetailed(txPurpose, txs...)
return errors.Join(errs...)
} }
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
) )
func TestSendAndWait(t *testing.T) { func TestSendAndWaitQueueWithMaxPending(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
txMgr := &stubTxMgr{sending: make(map[byte]chan *types.Receipt)} txMgr := &stubTxMgr{sending: make(map[byte]chan *types.Receipt)}
...@@ -26,18 +26,18 @@ func TestSendAndWait(t *testing.T) { ...@@ -26,18 +26,18 @@ func TestSendAndWait(t *testing.T) {
return txmgr.TxCandidate{TxData: []byte{i}} return txmgr.TxCandidate{TxData: []byte{i}}
} }
sendAsync := func(txs ...txmgr.TxCandidate) chan []*types.Receipt { sendAsync := func(txs ...txmgr.TxCandidate) chan []txmgr.TxCandidate {
ch := make(chan []*types.Receipt, 1) ch := make(chan []txmgr.TxCandidate, 1)
go func() { go func() {
rcpts, err := sender.SendAndWait("testing", txs...) err := sender.SendAndWaitSimple("testing", txs...)
require.NoError(t, err) require.NoError(t, err)
ch <- rcpts ch <- txs
close(ch) close(ch)
}() }()
return ch return ch
} }
wait := func(ch chan []*types.Receipt) []*types.Receipt { wait := func(ch chan []txmgr.TxCandidate) []txmgr.TxCandidate {
select { select {
case rcpts := <-ch: case rcpts := <-ch:
return rcpts return rcpts
...@@ -88,9 +88,34 @@ func TestSendAndWait(t *testing.T) { ...@@ -88,9 +88,34 @@ func TestSendAndWait(t *testing.T) {
require.Len(t, wait(batch2), 2, "Batch2 should complete") require.Len(t, wait(batch2), 2, "Batch2 should complete")
} }
func TestSendAndWaitReturnIndividualErrors(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
txMgr := &stubTxMgr{
sending: make(map[byte]chan *types.Receipt),
syncStatus: map[byte]uint64{
0: types.ReceiptStatusSuccessful,
1: types.ReceiptStatusFailed,
2: types.ReceiptStatusSuccessful,
},
}
sender := NewTxSender(ctx, testlog.Logger(t, log.LevelInfo), txMgr, 500)
tx := func(i byte) txmgr.TxCandidate {
return txmgr.TxCandidate{TxData: []byte{i}}
}
errs := sender.SendAndWaitDetailed("testing", tx(0), tx(1), tx(2))
require.Len(t, errs, 3)
require.NoError(t, errs[0])
require.ErrorIs(t, errs[1], ErrTransactionReverted)
require.NoError(t, errs[2])
}
type stubTxMgr struct { type stubTxMgr struct {
m sync.Mutex m sync.Mutex
sending map[byte]chan *types.Receipt sending map[byte]chan *types.Receipt
syncStatus map[byte]uint64
} }
func (s *stubTxMgr) IsClosed() bool { func (s *stubTxMgr) IsClosed() bool {
...@@ -111,7 +136,11 @@ func (s *stubTxMgr) recordTx(candidate txmgr.TxCandidate) chan *types.Receipt { ...@@ -111,7 +136,11 @@ func (s *stubTxMgr) recordTx(candidate txmgr.TxCandidate) chan *types.Receipt {
panic("Sending duplicate transaction") panic("Sending duplicate transaction")
} }
ch := make(chan *types.Receipt, 1) ch := make(chan *types.Receipt, 1)
s.sending[id] = ch if status, ok := s.syncStatus[id]; ok {
ch <- &types.Receipt{Status: status}
} else {
s.sending[id] = ch
}
return ch return ch
} }
......
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