Skip to content

Commit

Permalink
op-proposer: retry failed output proposals (#11291)
Browse files Browse the repository at this point in the history
* op-proposer: add retries to output proposal

* op-proposer: proposeOutput returns err to help trigger retry

* op-proposer: use retry.Do for FetchOutput, add unit tests

* op-proposer: improve output fetching retry impl

* op-proposer: move done signal check into inner loop

---------

Co-authored-by: Sebastian Stammler <[email protected]>
  • Loading branch information
bitwiseguy and sebastianst authored Aug 6, 2024
1 parent f940301 commit 052ca79
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 92 deletions.
15 changes: 6 additions & 9 deletions op-e2e/actions/l2_proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"math/big"
"time"

Expand Down Expand Up @@ -31,6 +32,7 @@ type ProposerCfg struct {
OutputOracleAddr *common.Address
DisputeGameFactoryAddr *common.Address
ProposalInterval time.Duration
ProposalRetryInterval time.Duration
DisputeGameType uint32
ProposerKey *ecdsa.PrivateKey
AllowNonFinalized bool
Expand Down Expand Up @@ -77,6 +79,7 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
PollInterval: time.Second,
NetworkTimeout: time.Second,
ProposalInterval: cfg.ProposalInterval,
OutputRetryInterval: cfg.ProposalRetryInterval,
L2OutputOracleAddr: cfg.OutputOracleAddr,
DisputeGameFactoryAddr: cfg.DisputeGameFactoryAddr,
DisputeGameType: cfg.DisputeGameType,
Expand Down Expand Up @@ -206,18 +209,12 @@ func toCallArg(msg ethereum.CallMsg) interface{} {

func (p *L2Proposer) fetchNextOutput(t Testing) (*eth.OutputResponse, bool, error) {
if e2eutils.UseFaultProofs() {
blockNumber, err := p.driver.FetchCurrentBlockNumber(t.Ctx())
output, err := p.driver.FetchDGFOutput(t.Ctx())
if err != nil {
return nil, false, err
}

output, _, err := p.driver.FetchOutput(t.Ctx(), blockNumber)
if err != nil {
return nil, false, err
}

encodedBlockNumber := make([]byte, 32)
copy(encodedBlockNumber[32-len(blockNumber.Bytes()):], blockNumber.Bytes())
binary.BigEndian.PutUint64(encodedBlockNumber[24:], output.BlockRef.Number)
game, err := p.disputeGameFactory.Games(&bind.CallOpts{}, p.driver.Cfg.DisputeGameType, output.OutputRoot, encodedBlockNumber)
if err != nil {
return nil, false, err
Expand All @@ -228,7 +225,7 @@ func (p *L2Proposer) fetchNextOutput(t Testing) (*eth.OutputResponse, bool, erro

return output, true, nil
} else {
return p.driver.FetchNextOutputInfo(t.Ctx())
return p.driver.FetchL2OOOutput(t.Ctx())
}
}

Expand Down
8 changes: 5 additions & 3 deletions op-e2e/actions/l2_proposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,17 @@ func RunProposerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) {
proposer = NewL2Proposer(t, log, &ProposerCfg{
DisputeGameFactoryAddr: &sd.DeploymentsL1.DisputeGameFactoryProxy,
ProposalInterval: 6 * time.Second,
ProposalRetryInterval: 3 * time.Second,
DisputeGameType: respectedGameType,
ProposerKey: dp.Secrets.Proposer,
AllowNonFinalized: true,
}, miner.EthClient(), rollupSeqCl)
} else {
proposer = NewL2Proposer(t, log, &ProposerCfg{
OutputOracleAddr: &sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer,
AllowNonFinalized: false,
OutputOracleAddr: &sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer,
ProposalRetryInterval: 3 * time.Second,
AllowNonFinalized: false,
}, miner.EthClient(), rollupSeqCl)
}

Expand Down
8 changes: 5 additions & 3 deletions op-e2e/actions/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,17 @@ func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) {
proposer = NewL2Proposer(t, log, &ProposerCfg{
DisputeGameFactoryAddr: &sd.DeploymentsL1.DisputeGameFactoryProxy,
ProposalInterval: 6 * time.Second,
ProposalRetryInterval: 3 * time.Second,
DisputeGameType: respectedGameType,
ProposerKey: dp.Secrets.Proposer,
AllowNonFinalized: true,
}, miner.EthClient(), seq.RollupClient())
} else {
proposer = NewL2Proposer(t, log, &ProposerCfg{
OutputOracleAddr: &sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer,
AllowNonFinalized: true,
OutputOracleAddr: &sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer,
ProposalRetryInterval: 3 * time.Second,
AllowNonFinalized: true,
}, miner.EthClient(), seq.RollupClient())
}

Expand Down
30 changes: 16 additions & 14 deletions op-e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -845,27 +845,29 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
var proposerCLIConfig *l2os.CLIConfig
if e2eutils.UseFaultProofs() {
proposerCLIConfig = &l2os.CLIConfig{
L1EthRpc: sys.EthInstances[RoleL1].WSEndpoint(),
RollupRpc: sys.RollupNodes[RoleSeq].HTTPEndpoint(),
DGFAddress: config.L1Deployments.DisputeGameFactoryProxy.Hex(),
ProposalInterval: 6 * time.Second,
DisputeGameType: 254, // Fast game type
PollInterval: 50 * time.Millisecond,
TxMgrConfig: newTxMgrConfig(sys.EthInstances[RoleL1].WSEndpoint(), cfg.Secrets.Proposer),
AllowNonFinalized: cfg.NonFinalizedProposals,
L1EthRpc: sys.EthInstances[RoleL1].WSEndpoint(),
RollupRpc: sys.RollupNodes[RoleSeq].HTTPEndpoint(),
DGFAddress: config.L1Deployments.DisputeGameFactoryProxy.Hex(),
ProposalInterval: 6 * time.Second,
DisputeGameType: 254, // Fast game type
PollInterval: 50 * time.Millisecond,
OutputRetryInterval: 10 * time.Millisecond,
TxMgrConfig: newTxMgrConfig(sys.EthInstances[RoleL1].WSEndpoint(), cfg.Secrets.Proposer),
AllowNonFinalized: cfg.NonFinalizedProposals,
LogConfig: oplog.CLIConfig{
Level: log.LvlInfo,
Format: oplog.FormatText,
},
}
} else {
proposerCLIConfig = &l2os.CLIConfig{
L1EthRpc: sys.EthInstances[RoleL1].WSEndpoint(),
RollupRpc: sys.RollupNodes[RoleSeq].HTTPEndpoint(),
L2OOAddress: config.L1Deployments.L2OutputOracleProxy.Hex(),
PollInterval: 50 * time.Millisecond,
TxMgrConfig: newTxMgrConfig(sys.EthInstances[RoleL1].WSEndpoint(), cfg.Secrets.Proposer),
AllowNonFinalized: cfg.NonFinalizedProposals,
L1EthRpc: sys.EthInstances[RoleL1].WSEndpoint(),
RollupRpc: sys.RollupNodes[RoleSeq].HTTPEndpoint(),
L2OOAddress: config.L1Deployments.L2OutputOracleProxy.Hex(),
PollInterval: 50 * time.Millisecond,
OutputRetryInterval: 10 * time.Millisecond,
TxMgrConfig: newTxMgrConfig(sys.EthInstances[RoleL1].WSEndpoint(), cfg.Secrets.Proposer),
AllowNonFinalized: cfg.NonFinalizedProposals,
LogConfig: oplog.CLIConfig{
Level: log.LvlInfo,
Format: oplog.FormatText,
Expand Down
11 changes: 9 additions & 2 deletions op-proposer/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ var (
}
PollIntervalFlag = &cli.DurationFlag{
Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks",
Value: 6 * time.Second,
Usage: "How frequently to poll L2 for new blocks (legacy L2OO)",
Value: 12 * time.Second,
EnvVars: prefixEnvVars("POLL_INTERVAL"),
}
AllowNonFinalizedFlag = &cli.BoolFlag{
Expand All @@ -60,6 +60,12 @@ var (
Usage: "Interval between submitting L2 output proposals when the dispute game factory address is set",
EnvVars: prefixEnvVars("PROPOSAL_INTERVAL"),
}
OutputRetryIntervalFlag = &cli.DurationFlag{
Name: "output-retry-interval",
Usage: "Interval between retrying output fetching (DGF)",
Value: 12 * time.Second,
EnvVars: prefixEnvVars("OUTPUT_RETRY_INTERVAL"),
}
DisputeGameTypeFlag = &cli.UintFlag{
Name: "game-type",
Usage: "Dispute game type to create via the configured DisputeGameFactory",
Expand Down Expand Up @@ -95,6 +101,7 @@ var optionalFlags = []cli.Flag{
L2OutputHDPathFlag,
DisputeGameFactoryAddressFlag,
ProposalIntervalFlag,
OutputRetryIntervalFlag,
DisputeGameTypeFlag,
ActiveSequencerCheckDurationFlag,
WaitNodeSyncFlag,
Expand Down
4 changes: 4 additions & 0 deletions op-proposer/proposer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type CLIConfig struct {
// ProposalInterval is the delay between submitting L2 output proposals when the DGFAddress is set.
ProposalInterval time.Duration

// OutputRetryInterval is the delay between retrying output fetch if one fails.
OutputRetryInterval time.Duration

// DisputeGameType is the type of dispute game to create when submitting an output proposal.
DisputeGameType uint32

Expand Down Expand Up @@ -110,6 +113,7 @@ func NewConfig(ctx *cli.Context) *CLIConfig {
PprofConfig: oppprof.ReadCLIConfig(ctx),
DGFAddress: ctx.String(flags.DisputeGameFactoryAddressFlag.Name),
ProposalInterval: ctx.Duration(flags.ProposalIntervalFlag.Name),
OutputRetryInterval: ctx.Duration(flags.OutputRetryIntervalFlag.Name),
DisputeGameType: uint32(ctx.Uint(flags.DisputeGameTypeFlag.Name)),
ActiveSequencerCheckDuration: ctx.Duration(flags.ActiveSequencerCheckDurationFlag.Name),
WaitNodeSync: ctx.Bool(flags.WaitNodeSyncFlag.Name),
Expand Down
Loading

0 comments on commit 052ca79

Please sign in to comment.