Skip to content

Commit

Permalink
Improved inactivity time synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed May 9, 2024
1 parent e76d60d commit 9ad6371
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 34 deletions.
49 changes: 31 additions & 18 deletions pkg/tbtc/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ import (
)

const (
// heartbeatProposalValidityBlocks determines the wallet heartbeat proposal
// validity time expressed in blocks. In other words, this is the worst-case
// time for a wallet heartbeat during which the wallet is busy and cannot
// take another actions. The value of 300 blocks is roughly 1 hour, assuming
// 12 seconds per block.
heartbeatProposalValidityBlocks = 300
// heartbeatTotalProposalValidityBlocks determines the total wallet
// heartbeat proposal validity time expressed in blocks. In other words,
// this is the worst-case time for a wallet heartbeat during which the
// wallet is busy and cannot take another actions. It includes the total
// duration need to perform both both signing the heartbeat message and
// optionally notifying about operator inactivity if the heartbeat failed.
// The value of 600 blocks is roughly 2 hours, assuming 12 seconds per block.
heartbeatTotalProposalValidityBlocks = 600
// heartbeatInactivityNotificationValidityBlocks determines the duration
// that needs to be preserved for the optional notification about operator
// inactivity that follows a failed heartbeat signing.
heartbeatInactivityNotificationValidityBlocks = 300
// heartbeatRequestTimeoutSafetyMarginBlocks determines the duration of the
// safety margin that must be preserved between the signing timeout
// and the timeout of the entire heartbeat action. This safety
// margin prevents against the case where signing completes too late and
// another action has been already requested by the coordinator.
// safety margin that must be preserved between the timeout of operator
// inactivity notification and the timeout of the entire heartbeat action.
// This safety margin prevents against the case where signing completes too
// late and another action has been already requested by the coordinator.
// The value of 25 blocks is roughly 5 minutes, assuming 12 seconds per block.
heartbeatRequestTimeoutSafetyMarginBlocks = 25
// heartbeatSigningMinimumActiveOperators determines the minimum number of
Expand All @@ -44,7 +50,7 @@ func (hp *HeartbeatProposal) ActionType() WalletActionType {
}

func (hp *HeartbeatProposal) ValidityBlocks() uint64 {
return heartbeatProposalValidityBlocks
return heartbeatTotalProposalValidityBlocks
}

// heartbeatSigningExecutor is an interface meant to decouple the specific
Expand Down Expand Up @@ -136,19 +142,19 @@ func (ha *heartbeatAction) execute() error {
messageToSign := new(big.Int).SetBytes(messageBytes[:])

// Just in case. This should never happen.
if ha.expiryBlock < heartbeatRequestTimeoutSafetyMarginBlocks {
if ha.expiryBlock < heartbeatInactivityNotificationValidityBlocks {
return fmt.Errorf("invalid proposal expiry block")
}

heartbeatCtx, cancelHeartbeatCtx := withCancelOnBlock(
heartbeatSigningCtx, cancelHeartbeatSigningCtx := withCancelOnBlock(
context.Background(),
ha.expiryBlock-heartbeatRequestTimeoutSafetyMarginBlocks,
ha.expiryBlock-heartbeatInactivityNotificationValidityBlocks,
ha.waitForBlockFn,
)
defer cancelHeartbeatCtx()
defer cancelHeartbeatSigningCtx()

signature, activeOperatorsCount, signingEndBlock, err := ha.signingExecutor.sign(
heartbeatCtx,
signature, activeOperatorsCount, _, err := ha.signingExecutor.sign(
heartbeatSigningCtx,
messageToSign,
ha.startBlock,
)
Expand Down Expand Up @@ -192,17 +198,24 @@ func (ha *heartbeatAction) execute() error {
return nil
}

heartbeatInactivityCtx, cancelHeartbeatInactivityCtx := withCancelOnBlock(
context.Background(),
ha.expiryBlock-heartbeatRequestTimeoutSafetyMarginBlocks,
ha.waitForBlockFn,
)
defer cancelHeartbeatInactivityCtx()

// The value of consecutive heartbeat failures exceeds the threshold.
// Proceed with operator inactivity notification.
err = ha.inactivityClaimExecutor.claimInactivity(
heartbeatInactivityCtx,
// Leave the list of inactive operators empty even if some operators
// were inactive during signing heartbeat. The inactive operators could
// simply be in the process of unstaking and therefore should not be
// punished.
[]group.MemberIndex{},
true,
messageToSign,
signingEndBlock,
)
if err != nil {
return fmt.Errorf(
Expand Down
4 changes: 2 additions & 2 deletions pkg/tbtc/heartbeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestHeartbeatAction_HappyPath(t *testing.T) {
}

startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks
expiryBlock := startBlock + heartbeatTotalProposalValidityBlocks

proposal := &HeartbeatProposal{
Message: [16]byte{
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestHeartbeatAction_SigningError(t *testing.T) {
}

startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks
expiryBlock := startBlock + heartbeatTotalProposalValidityBlocks

proposal := &HeartbeatProposal{
Message: [16]byte{
Expand Down
18 changes: 4 additions & 14 deletions pkg/tbtc/inactivity.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ const (
// that is used to calculate the submission delay period that should be respected
// by the given member to avoid all members submitting the same inactivity claim
// at the same time.
inactivityClaimSubmissionDelayStepBlocks = 3
// inactivityClaimMaximumSubmissionBlocks determines the maximum block
// duration of inactivity claim submission procedure.
inactivityClaimMaximumSubmissionBlocks = 60
inactivityClaimSubmissionDelayStepBlocks = 2
)

// errInactivityClaimExecutorBusy is an error returned when the inactivity claim
Expand Down Expand Up @@ -70,10 +67,10 @@ func newInactivityClaimExecutor(
}

func (ice *inactivityClaimExecutor) claimInactivity(
parentCtx context.Context,
inactiveMembersIndexes []group.MemberIndex,
heartbeatFailed bool,
sessionID *big.Int,
startBlock uint64,
) error {
if lockAcquired := ice.lock.TryAcquire(1); !lockAcquired {
return errInactivityClaimExecutorBusy
Expand All @@ -88,12 +85,9 @@ func (ice *inactivityClaimExecutor) claimInactivity(
return fmt.Errorf("cannot marshal wallet public key: [%v]", err)
}

timeoutBlock := startBlock + inactivityClaimMaximumSubmissionBlocks

execLogger := logger.With(
zap.String("wallet", fmt.Sprintf("0x%x", walletPublicKeyBytes)),
zap.Uint64("inactivityClaimStartBlock", startBlock),
zap.Uint64("inactivityClaimTimeoutBlock", timeoutBlock),
zap.String("sessionID", fmt.Sprintf("0x%x", sessionID)),
)

walletRegistryData, err := ice.chain.GetWallet(walletPublicKeyHash)
Expand Down Expand Up @@ -135,11 +129,7 @@ func (ice *inactivityClaimExecutor) claimInactivity(
signer.signingGroupMemberIndex,
)

ctx, cancelCtx := withCancelOnBlock(
context.Background(),
timeoutBlock,
ice.waitForBlockFn,
)
ctx, cancelCtx := context.WithCancel(parentCtx)
defer cancelCtx()

subscription := ice.chain.OnInactivityClaimed(
Expand Down

0 comments on commit 9ad6371

Please sign in to comment.