diff --git a/assertions/manager.go b/assertions/manager.go index f0a2cb8b3..cb13a944c 100644 --- a/assertions/manager.go +++ b/assertions/manager.go @@ -41,17 +41,19 @@ var ( ) type timings struct { - pollInterval time.Duration - confInterval time.Duration - postInterval time.Duration - avgBlockTime time.Duration + pollInterval time.Duration + confInterval time.Duration + postInterval time.Duration + avgBlockTime time.Duration + minGapToParent time.Duration } var defaultTimings = timings{ - pollInterval: time.Minute, - confInterval: time.Second * 10, - postInterval: time.Hour, - avgBlockTime: time.Second * 12, + pollInterval: time.Minute, + confInterval: time.Second * 10, + postInterval: time.Hour, + avgBlockTime: time.Second * 12, + minGapToParent: time.Minute * 15, } // The Manager struct is responsible for several tasks related to the assertion @@ -190,6 +192,18 @@ func WithAverageBlockCreationTime(t time.Duration) Opt { } } +// WithMinimumGapToParentAssertion overrides the default minimum gap (in duration) +// to parent assertion creation time. +// +// The minimum gap to parent assertion is used by the assertion manager to wait +// until this much amount of duration is passed since the parent assertion was created +// before posting a new assertion. +func WithMinimumGapToParentAssertion(t time.Duration) Opt { + return func(m *Manager) { + m.times.minGapToParent = t + } +} + // NewManager creates a manager from the required dependencies. func NewManager( chain protocol.AssertionChain, diff --git a/assertions/manager_test.go b/assertions/manager_test.go index 71c9b0aa7..7839963e3 100644 --- a/assertions/manager_test.go +++ b/assertions/manager_test.go @@ -124,6 +124,7 @@ func TestSkipsProcessingAssertionFromEvilFork(t *testing.T) { types.DefensiveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), assertions.WithPostingDisabled(), ) require.NoError(t, err) @@ -306,6 +307,7 @@ func TestComplexAssertionForkScenario(t *testing.T) { types.DefensiveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), assertions.WithPostingDisabled(), ) require.NoError(t, err) @@ -377,6 +379,7 @@ func TestFastConfirmation(t *testing.T) { types.ResolveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), assertions.WithFastConfirmation(), ) require.NoError(t, err) @@ -449,6 +452,7 @@ func TestFastConfirmationWithSafe(t *testing.T) { types.ResolveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), assertions.WithDangerousReadyToPost(), assertions.WithPostingDisabled(), assertions.WithFastConfirmation(), @@ -490,6 +494,7 @@ func TestFastConfirmationWithSafe(t *testing.T) { types.ResolveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), assertions.WithDangerousReadyToPost(), assertions.WithPostingDisabled(), assertions.WithFastConfirmation(), diff --git a/assertions/poster.go b/assertions/poster.go index dc5fe90cf..f9ddf4af7 100644 --- a/assertions/poster.go +++ b/assertions/poster.go @@ -7,8 +7,10 @@ package assertions import ( "context" "fmt" + "math/big" "time" + "github.com/ccoveille/go-safecast" "github.com/pkg/errors" "github.com/ethereum/go-ethereum/log" @@ -37,36 +39,33 @@ func (m *Manager) postAssertionRoutine(ctx context.Context) { exceedsMaxMempoolSizeEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "posting this transaction will exceed max mempool size", 0) log.Info("Ready to post") - if _, err := m.PostAssertion(ctx); err != nil { - if !errors.Is(err, solimpl.ErrAlreadyExists) { - logLevel := log.Error - logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) - - logLevel("Could not submit latest assertion to L1", "err", err) - errorPostingAssertionCounter.Inc(1) - } - } ticker := time.NewTicker(m.times.postInterval) defer ticker.Stop() for { - select { - case <-ticker.C: - _, err := m.PostAssertion(ctx) - if err != nil { - switch { - case errors.Is(err, solimpl.ErrAlreadyExists): - case errors.Is(err, solimpl.ErrBatchNotYetFound): - log.Info("Waiting for more batches to post assertions about them onchain") - default: - logLevel := log.Error - logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) - - logLevel("Could not submit latest assertion", "err", err, "validatorName", m.validatorName) - errorPostingAssertionCounter.Inc(1) + _, err := m.PostAssertion(ctx) + if err != nil { + switch { + case errors.Is(err, solimpl.ErrAlreadyExists): + case errors.Is(err, solimpl.ErrBatchNotYetFound): + log.Info("Waiting for more batches to post assertions about them onchain") + default: + logLevel := log.Error + logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) + + logLevel("Could not submit latest assertion", "err", err, "validatorName", m.validatorName) + errorPostingAssertionCounter.Inc(1) + + if ctx.Err() != nil { + return } - } else { - exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + continue // We retry again in case of a non ctx error } + } else { + exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + } + + select { + case <-ticker.C: case <-ctx.Done(): return } @@ -161,10 +160,12 @@ func (m *Manager) PostAssertionBasedOnParent( return none, errors.Wrapf(err, "could not get execution state at batch count %d with parent block hash %v", batchCount, parentBlockHash) } - // If the assertion is not an overflow assertion (has a 0 position in batch), - // then should check if we need to wait for the minimum number of blocks in between - // assertions. Overflow ones are not subject to this check onchain. - if newState.GlobalState.PosInBatch == 0 { + // If the assertion is not an overflow assertion i.e !(newState.GlobalState.Batch < batchCount) derived from + // contracts check for overflow assertion => assertion.afterState.globalState.u64Vals[0] < assertion.beforeStateData.configData.nextInboxPosition) + // then should check if we need to wait for the minimum number of blocks between assertions and a minimum time since parent assertion creation. + // Overflow ones are not subject to this check onchain. + isOverflowAssertion := newState.MachineStatus != protocol.MachineStatusErrored && newState.GlobalState.Batch < batchCount + if !isOverflowAssertion { if err = m.waitToPostIfNeeded(ctx, parentCreationInfo); err != nil { return none, err } @@ -203,6 +204,18 @@ func (m *Manager) waitToPostIfNeeded( ctx context.Context, parentCreationInfo *protocol.AssertionCreatedInfo, ) error { + if m.times.minGapToParent != 0 { + parentCreationBlock, err := m.backend.HeaderByNumber(ctx, new(big.Int).SetUint64(parentCreationInfo.CreationBlock)) + if err != nil { + return fmt.Errorf("error getting parent assertion creation block header: %w", err) + } + parentCreationTime, err := safecast.ToInt64(parentCreationBlock.Time) + if err != nil { + return fmt.Errorf("error casting parent assertion creation time to int64: %w", err) + } + targetTime := time.Unix(parentCreationTime, 0).Add(m.times.minGapToParent) + time.Sleep(time.Until(targetTime)) + } minPeriodBlocks := m.chain.MinAssertionPeriodBlocks() for { latestBlockNumber, err := m.backend.HeaderU64(ctx) diff --git a/assertions/poster_test.go b/assertions/poster_test.go index eb7f2fd52..44c316f6b 100644 --- a/assertions/poster_test.go +++ b/assertions/poster_test.go @@ -55,6 +55,9 @@ func TestPostAssertion(t *testing.T) { stateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) require.NoError(t, err) + // Set MinimumGapToBlockCreationTime as 1 second to verify that a new assertion is only posted after 1 sec has passed + // from parent assertion creation. This will make the test run for ~19 seconds as the parent assertion time is + // ~18 seconds in the future assertionManager, err := assertions.NewManager( aliceChain, stateManager, @@ -62,6 +65,7 @@ func TestPostAssertion(t *testing.T) { types.DefensiveMode, assertions.WithPollingInterval(time.Millisecond*200), assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(time.Second), ) require.NoError(t, err) diff --git a/challenge-manager/stack.go b/challenge-manager/stack.go index f5722c3d6..4107fb2f6 100644 --- a/challenge-manager/stack.go +++ b/challenge-manager/stack.go @@ -28,6 +28,7 @@ type stackParams struct { postInterval time.Duration confInterval time.Duration avgBlockTime time.Duration + minGapToParent time.Duration trackChallengeParentAssertionHashes []protocol.AssertionHash apiAddr string apiDBPath string @@ -47,6 +48,7 @@ var defaultStackParams = stackParams{ postInterval: time.Hour, confInterval: time.Second * 10, avgBlockTime: time.Second * 12, + minGapToParent: time.Minute * 10, trackChallengeParentAssertionHashes: nil, apiAddr: "", apiDBPath: "", @@ -106,6 +108,14 @@ func StackWithAverageBlockCreationTime(interval time.Duration) StackOpt { } } +// StackWithMinimumGapToParentAssertion sets the minimum gap to parent assertion creation time +// of the challenge manager. +func StackWithMinimumGapToParentAssertion(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.minGapToParent = interval + } +} + // WithTrackChallengeParentAssertionHashes sets the track challenge parent // assertion hashes of the challenge manager. func StackWithTrackChallengeParentAssertionHashes(hashes []string) StackOpt { @@ -243,6 +253,7 @@ func NewChallengeStack( assertions.WithConfirmationInterval(params.confInterval), assertions.WithPollingInterval(params.pollInterval), assertions.WithPostingInterval(params.postInterval), + assertions.WithMinimumGapToParentAssertion(params.minGapToParent), } if apiDB != nil { amOpts = append(amOpts, assertions.WithAPIDB(apiDB)) diff --git a/testing/endtoend/e2e_crash_test.go b/testing/endtoend/e2e_crash_test.go index 4b1f11f01..5da4578f3 100644 --- a/testing/endtoend/e2e_crash_test.go +++ b/testing/endtoend/e2e_crash_test.go @@ -99,6 +99,7 @@ func TestEndToEnd_HonestValidatorCrashes(t *testing.T) { cm.StackWithPostingInterval(timeCfg.assertionPostingInterval), cm.StackWithAverageBlockCreationTime(timeCfg.blockTime), cm.StackWithConfirmationInterval(timeCfg.assertionConfirmationAttemptInterval), + cm.StackWithMinimumGapToParentAssertion(0), cm.StackWithHeaderProvider(shp), } diff --git a/testing/endtoend/e2e_test.go b/testing/endtoend/e2e_test.go index 43da9d246..675bc41d1 100644 --- a/testing/endtoend/e2e_test.go +++ b/testing/endtoend/e2e_test.go @@ -266,6 +266,7 @@ func runEndToEndTest(t *testing.T, cfg *e2eConfig) { cm.StackWithPostingInterval(cfg.timings.assertionPostingInterval), cm.StackWithAverageBlockCreationTime(cfg.timings.blockTime), cm.StackWithConfirmationInterval(cfg.timings.assertionConfirmationAttemptInterval), + cm.StackWithMinimumGapToParentAssertion(0), cm.StackWithHeaderProvider(shp), }