Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait for a minimum amount of time since parent assertion was created to post a new assertion #714

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
30 changes: 22 additions & 8 deletions assertions/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions assertions/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
71 changes: 42 additions & 29 deletions assertions/poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions assertions/poster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ 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,
"alice",
types.DefensiveMode,
assertions.WithPollingInterval(time.Millisecond*200),
assertions.WithAverageBlockCreationTime(time.Second),
assertions.WithMinimumGapToParentAssertion(time.Second),
)
require.NoError(t, err)

Expand Down
11 changes: 11 additions & 0 deletions challenge-manager/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: "",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions testing/endtoend/e2e_crash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
1 change: 1 addition & 0 deletions testing/endtoend/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
Loading