Commit d356d92a authored by PierreOssun's avatar PierreOssun Committed by GitHub

op-proposer: (DFG) skip new game creation if outputRoot is unchanged (#13288)

* op-proposer: do not propose if outputRoot is the same as last proposed game

* fixed porposer tests

* PR comments

* Fix log message.

---------
Co-authored-by: default avatarAdrian Sutton <adrian@symphonious.net>
Co-authored-by: default avatarAdrian Sutton <adrian@oplabs.co>
parent 90c18bcd
...@@ -29,6 +29,7 @@ type gameMetadata struct { ...@@ -29,6 +29,7 @@ type gameMetadata struct {
Timestamp time.Time Timestamp time.Time
Address common.Address Address common.Address
Proposer common.Address Proposer common.Address
Claim common.Hash
} }
type DisputeGameFactory struct { type DisputeGameFactory struct {
...@@ -62,30 +63,30 @@ func (f *DisputeGameFactory) Version(ctx context.Context) (string, error) { ...@@ -62,30 +63,30 @@ func (f *DisputeGameFactory) Version(ctx context.Context) (string, error) {
// HasProposedSince attempts to find a game with the specified game type created by the specified proposer after the // HasProposedSince attempts to find a game with the specified game type created by the specified proposer after the
// given cut off time. If one is found, returns true and the time the game was created at. // given cut off time. If one is found, returns true and the time the game was created at.
// If no matching proposal is found, returns false, time.Time{}, nil // If no matching proposal is found, returns false, time.Time{}, nil
func (f *DisputeGameFactory) HasProposedSince(ctx context.Context, proposer common.Address, cutoff time.Time, gameType uint32) (bool, time.Time, error) { func (f *DisputeGameFactory) HasProposedSince(ctx context.Context, proposer common.Address, cutoff time.Time, gameType uint32) (bool, time.Time, common.Hash, error) {
gameCount, err := f.gameCount(ctx) gameCount, err := f.gameCount(ctx)
if err != nil { if err != nil {
return false, time.Time{}, fmt.Errorf("failed to get dispute game count: %w", err) return false, time.Time{}, common.Hash{}, fmt.Errorf("failed to get dispute game count: %w", err)
} }
if gameCount == 0 { if gameCount == 0 {
return false, time.Time{}, nil return false, time.Time{}, common.Hash{}, nil
} }
for idx := gameCount - 1; ; idx-- { for idx := gameCount - 1; ; idx-- {
game, err := f.gameAtIndex(ctx, idx) game, err := f.gameAtIndex(ctx, idx)
if err != nil { if err != nil {
return false, time.Time{}, fmt.Errorf("failed to get dispute game %d: %w", idx, err) return false, time.Time{}, common.Hash{}, fmt.Errorf("failed to get dispute game %d: %w", idx, err)
} }
if game.Timestamp.Before(cutoff) { if game.Timestamp.Before(cutoff) {
// Reached a game that is before the expected cutoff, so we haven't found a suitable proposal // Reached a game that is before the expected cutoff, so we haven't found a suitable proposal
return false, time.Time{}, nil return false, time.Time{}, common.Hash{}, nil
} }
if game.GameType == gameType && game.Proposer == proposer { if game.GameType == gameType && game.Proposer == proposer {
// Found a matching proposal // Found a matching proposal
return true, game.Timestamp, nil return true, game.Timestamp, game.Claim, nil
} }
if idx == 0 { // Need to check here rather than in the for condition to avoid underflow if idx == 0 { // Need to check here rather than in the for condition to avoid underflow
// Checked every game and didn't find a match // Checked every game and didn't find a match
return false, time.Time{}, nil return false, time.Time{}, common.Hash{}, nil
} }
} }
} }
...@@ -135,13 +136,15 @@ func (f *DisputeGameFactory) gameAtIndex(ctx context.Context, idx uint64) (gameM ...@@ -135,13 +136,15 @@ func (f *DisputeGameFactory) gameAtIndex(ctx context.Context, idx uint64) (gameM
if err != nil { if err != nil {
return gameMetadata{}, fmt.Errorf("failed to load root claim of game %v: %w", idx, err) return gameMetadata{}, fmt.Errorf("failed to load root claim of game %v: %w", idx, err)
} }
// We don't need most of the claim data, only the claimant which is the game proposer // We don't need most of the claim data, only the claim and the claimant which is the game proposer
claimant := result.GetAddress(2) claimant := result.GetAddress(2)
claim := result.GetHash(4)
return gameMetadata{ return gameMetadata{
GameType: gameType, GameType: gameType,
Timestamp: time.Unix(int64(timestamp), 0), Timestamp: time.Unix(int64(timestamp), 0),
Address: address, Address: address,
Proposer: claimant, Proposer: claimant,
Claim: claim,
}, nil }, nil
} }
...@@ -25,10 +25,11 @@ func TestHasProposedSince(t *testing.T) { ...@@ -25,10 +25,11 @@ func TestHasProposedSince(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t) stubRpc, factory := setupDisputeGameFactoryTest(t)
withClaims(stubRpc) withClaims(stubRpc)
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.False(t, proposed) require.False(t, proposed)
require.Equal(t, time.Time{}, proposalTime) require.Equal(t, time.Time{}, proposalTime)
require.Equal(t, common.Hash{}, claim)
}) })
t.Run("NoMatchingProposal", func(t *testing.T) { t.Run("NoMatchingProposal", func(t *testing.T) {
...@@ -49,10 +50,11 @@ func TestHasProposedSince(t *testing.T) { ...@@ -49,10 +50,11 @@ func TestHasProposedSince(t *testing.T) {
}, },
) )
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.False(t, proposed) require.False(t, proposed)
require.Equal(t, time.Time{}, proposalTime) require.Equal(t, time.Time{}, proposalTime)
require.Equal(t, common.Hash{}, claim)
}) })
t.Run("MatchingProposalBeforeCutOff", func(t *testing.T) { t.Run("MatchingProposalBeforeCutOff", func(t *testing.T) {
...@@ -79,10 +81,11 @@ func TestHasProposedSince(t *testing.T) { ...@@ -79,10 +81,11 @@ func TestHasProposedSince(t *testing.T) {
}, },
) )
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.False(t, proposed) require.False(t, proposed)
require.Equal(t, time.Time{}, proposalTime) require.Equal(t, time.Time{}, proposalTime)
require.Equal(t, common.Hash{}, claim)
}) })
t.Run("MatchingProposalAtCutOff", func(t *testing.T) { t.Run("MatchingProposalAtCutOff", func(t *testing.T) {
...@@ -109,10 +112,11 @@ func TestHasProposedSince(t *testing.T) { ...@@ -109,10 +112,11 @@ func TestHasProposedSince(t *testing.T) {
}, },
) )
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.True(t, proposed) require.True(t, proposed)
require.Equal(t, cutOffTime, proposalTime) require.Equal(t, cutOffTime, proposalTime)
require.Equal(t, common.Hash{0xdd}, claim)
}) })
t.Run("MatchingProposalAfterCutOff", func(t *testing.T) { t.Run("MatchingProposalAfterCutOff", func(t *testing.T) {
...@@ -140,10 +144,11 @@ func TestHasProposedSince(t *testing.T) { ...@@ -140,10 +144,11 @@ func TestHasProposedSince(t *testing.T) {
}, },
) )
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.True(t, proposed) require.True(t, proposed)
require.Equal(t, expectedProposalTime, proposalTime) require.Equal(t, expectedProposalTime, proposalTime)
require.Equal(t, common.Hash{0xdd}, claim)
}) })
t.Run("MultipleMatchingProposalAfterCutOff", func(t *testing.T) { t.Run("MultipleMatchingProposalAfterCutOff", func(t *testing.T) {
...@@ -171,11 +176,12 @@ func TestHasProposedSince(t *testing.T) { ...@@ -171,11 +176,12 @@ func TestHasProposedSince(t *testing.T) {
}, },
) )
proposed, proposalTime, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0) proposed, proposalTime, claim, err := factory.HasProposedSince(context.Background(), proposerAddr, cutOffTime, 0)
require.NoError(t, err) require.NoError(t, err)
require.True(t, proposed) require.True(t, proposed)
// Should find the most recent proposal // Should find the most recent proposal
require.Equal(t, expectedProposalTime, proposalTime) require.Equal(t, expectedProposalTime, proposalTime)
require.Equal(t, common.Hash{0xdd}, claim)
}) })
} }
......
...@@ -46,7 +46,7 @@ type L2OOContract interface { ...@@ -46,7 +46,7 @@ type L2OOContract interface {
type DGFContract interface { type DGFContract interface {
Version(ctx context.Context) (string, error) Version(ctx context.Context) (string, error)
HasProposedSince(ctx context.Context, proposer common.Address, cutoff time.Time, gameType uint32) (bool, time.Time, error) HasProposedSince(ctx context.Context, proposer common.Address, cutoff time.Time, gameType uint32) (bool, time.Time, common.Hash, error)
ProposalTx(ctx context.Context, gameType uint32, outputRoot common.Hash, l2BlockNum uint64) (txmgr.TxCandidate, error) ProposalTx(ctx context.Context, gameType uint32, outputRoot common.Hash, l2BlockNum uint64) (txmgr.TxCandidate, error)
} }
...@@ -269,7 +269,7 @@ func (l *L2OutputSubmitter) FetchL2OOOutput(ctx context.Context) (*eth.OutputRes ...@@ -269,7 +269,7 @@ func (l *L2OutputSubmitter) FetchL2OOOutput(ctx context.Context) (*eth.OutputRes
// context will be derived from it. // context will be derived from it.
func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResponse, bool, error) { func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResponse, bool, error) {
cutoff := time.Now().Add(-l.Cfg.ProposalInterval) cutoff := time.Now().Add(-l.Cfg.ProposalInterval)
proposedRecently, proposalTime, err := l.dgfContract.HasProposedSince(ctx, l.Txmgr.From(), cutoff, l.Cfg.DisputeGameType) proposedRecently, proposalTime, claim, err := l.dgfContract.HasProposedSince(ctx, l.Txmgr.From(), cutoff, l.Cfg.DisputeGameType)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("could not check for recent proposal: %w", err) return nil, false, fmt.Errorf("could not check for recent proposal: %w", err)
} }
...@@ -278,7 +278,6 @@ func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResp ...@@ -278,7 +278,6 @@ func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResp
l.Log.Debug("Duration since last game not past proposal interval", "duration", time.Since(proposalTime)) l.Log.Debug("Duration since last game not past proposal interval", "duration", time.Since(proposalTime))
return nil, false, nil return nil, false, nil
} }
l.Log.Info("No proposals found for at least proposal interval, submitting proposal now", "proposalInterval", l.Cfg.ProposalInterval)
// Fetch the current L2 heads // Fetch the current L2 heads
currentBlockNumber, err := l.FetchCurrentBlockNumber(ctx) currentBlockNumber, err := l.FetchCurrentBlockNumber(ctx)
...@@ -296,6 +295,13 @@ func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResp ...@@ -296,6 +295,13 @@ func (l *L2OutputSubmitter) FetchDGFOutput(ctx context.Context) (*eth.OutputResp
return nil, false, fmt.Errorf("could not fetch output at current block number %d: %w", currentBlockNumber, err) return nil, false, fmt.Errorf("could not fetch output at current block number %d: %w", currentBlockNumber, err)
} }
if claim == common.Hash(output.OutputRoot) {
l.Log.Debug("Skipping proposal: output root unchanged since last proposed game", "last_proposed_root", claim, "output_root", output.OutputRoot)
return nil, false, nil
}
l.Log.Info("No proposals found for at least proposal interval, submitting proposal now", "proposalInterval", l.Cfg.ProposalInterval)
return output, true, nil return output, true, nil
} }
......
...@@ -42,9 +42,9 @@ type StubDGFContract struct { ...@@ -42,9 +42,9 @@ type StubDGFContract struct {
hasProposedCount int hasProposedCount int
} }
func (m *StubDGFContract) HasProposedSince(_ context.Context, _ common.Address, _ time.Time, _ uint32) (bool, time.Time, error) { func (m *StubDGFContract) HasProposedSince(_ context.Context, _ common.Address, _ time.Time, _ uint32) (bool, time.Time, common.Hash, error) {
m.hasProposedCount++ m.hasProposedCount++
return false, time.Unix(1000, 0), nil return false, time.Unix(1000, 0), common.Hash{0xdd}, nil
} }
func (m *StubDGFContract) ProposalTx(_ context.Context, _ uint32, _ common.Hash, _ uint64) (txmgr.TxCandidate, error) { func (m *StubDGFContract) ProposalTx(_ context.Context, _ uint32, _ common.Hash, _ uint64) (txmgr.TxCandidate, error) {
......
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