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

Confirm DKG start before triggering off-chain protocol #3468

Merged
merged 8 commits into from
Jan 12, 2023
37 changes: 37 additions & 0 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,43 @@ func (tc *TbtcChain) OnDKGStarted(
return tc.walletRegistry.DkgStartedEvent(nil, nil).OnEvent(onEvent)
}

func (tc *TbtcChain) PastDKGStartedEvents(
filter *tbtc.DKGStartedEventFilter,
) ([]*tbtc.DKGStartedEvent, error) {
var startBlock uint64
var endBlock *uint64
var seed []*big.Int

if filter != nil {
startBlock = filter.StartBlock
endBlock = filter.EndBlock
seed = filter.Seed
}

events, err := tc.walletRegistry.PastDkgStartedEvents(
startBlock,
endBlock,
seed,
)
if err != nil {
return nil, err
}

dkgStartedEvents := make([]*tbtc.DKGStartedEvent, len(events))
for i, event := range events {
dkgStartedEvents[i] = &tbtc.DKGStartedEvent{
Seed: event.Seed,
BlockNumber: event.Raw.BlockNumber,
}
}

sort.SliceStable(dkgStartedEvents, func(i, j int) bool {
return dkgStartedEvents[i].BlockNumber < dkgStartedEvents[j].BlockNumber
})

return dkgStartedEvents, err
}

func (tc *TbtcChain) OnDKGResultSubmitted(
handler func(event *tbtc.DKGResultSubmittedEvent),
) subscription.EventSubscription {
Expand Down
15 changes: 15 additions & 0 deletions pkg/tbtc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ type DistributedKeyGenerationChain interface {
func(event *DKGStartedEvent),
) subscription.EventSubscription

// PastDKGStartedEvents fetches past DKG started events according to the
// provided filter or unfiltered if the filter is nil. Returned events
// are sorted by the block number in the ascending order, i.e. the latest
// event is at the end of the slice.
PastDKGStartedEvents(
filter *DKGStartedEventFilter,
) ([]*DKGStartedEvent, error)

// OnDKGResultSubmitted registers a callback that is invoked when an on-chain
// notification of the DKG result submission is seen.
OnDKGResultSubmitted(
Expand Down Expand Up @@ -131,6 +139,13 @@ type DKGStartedEvent struct {
BlockNumber uint64
}

// DKGStartedEventFilter is a component allowing to filter DKGStartedEvent.
type DKGStartedEventFilter struct {
StartBlock uint64
EndBlock *uint64
Seed []*big.Int
}

// DKGResultSubmittedEvent represents a DKG result submission event. It is
// emitted after a submitted DKG result lands on the chain.
type DKGResultSubmittedEvent struct {
Expand Down
6 changes: 6 additions & 0 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ func (lc *localChain) OnDKGStarted(
panic("unsupported")
}

func (lc *localChain) PastDKGStartedEvents(
filter *DKGStartedEventFilter,
) ([]*DKGStartedEvent, error) {
panic("unsupported")
}

func (lc *localChain) OnDKGResultSubmitted(
handler func(event *DKGResultSubmittedEvent),
) subscription.EventSubscription {
Expand Down
21 changes: 18 additions & 3 deletions pkg/tbtc/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
)

const (
// dkgStartedConfirmationBlocks determines the block length of the
// confirmation period that is preserved after a DKG start. Once the period
// elapses, the DKG state is checked to confirm the protocol can be started.
dkgStartedConfirmationBlocks = 20
// dkgResultSubmissionDelayStep determines the delay step in blocks that
// is used to calculate the submission delay period that should be respected
// by the given member to avoid all members submitting the same DKG result
Expand Down Expand Up @@ -104,10 +108,14 @@ func (de *dkgExecutor) preParamsCount() int {
// executeDkgIfEligible is the main function of dkgExecutor. It performs the
// full execution of ECDSA Distributed Key Generation: determining members
// selected to the signing group, executing off-chain protocol, and publishing
// the result to the chain.
// the result to the chain. The execution can be delayed by an arbitrary number
// of blocks using the delayBlocks argument. This allows confirming the state
// on-chain - e.g. wait for the required number of confirming blocks - before
//executing the off-chain action.
func (de *dkgExecutor) executeDkgIfEligible(
seed *big.Int,
startBlock uint64,
delayBlocks uint64,
) {
dkgLogger := logger.With(
zap.String("seed", fmt.Sprintf("0x%x", seed)),
Expand Down Expand Up @@ -145,6 +153,7 @@ func (de *dkgExecutor) executeDkgIfEligible(
memberIndexes,
groupSelectionResult,
startBlock,
delayBlocks,
)
} else {
dkgLogger.Infof("not eligible for DKG")
Expand Down Expand Up @@ -224,13 +233,19 @@ func (de *dkgExecutor) setupBroadcastChannel(

// generateSigningGroup executes off-chain protocol for each member controlled
// by the current operator and upon successful execution of the protocol
// publishes the result to the chain.
// publishes the result to the chain. The execution can be delayed by an
// arbitrary number of blocks using the delayBlocks argument. This allows
// confirming the state on-chain - e.g. wait for the required number of
// confirming blocks - before executing the off-chain action. Note that the
// startBlock represents the block at which DKG started on-chain. This is
// important for the result submission.
func (de *dkgExecutor) generateSigningGroup(
dkgLogger *zap.SugaredLogger,
seed *big.Int,
memberIndexes []uint8,
groupSelectionResult *GroupSelectionResult,
startBlock uint64,
delayBlocks uint64,
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
) {
membershipValidator := group.NewMembershipValidator(
dkgLogger,
Expand Down Expand Up @@ -296,7 +311,7 @@ func (de *dkgExecutor) generateSigningGroup(
retryLoop := newDkgRetryLoop(
dkgLogger,
seed,
startBlock,
startBlock+delayBlocks,
memberIndex,
groupSelectionResult.OperatorsAddresses,
de.groupParameters,
Expand Down
13 changes: 10 additions & 3 deletions pkg/tbtc/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,16 @@ func (n *node) operatorID() (chain.OperatorID, error) {
// distributed key generation if this node's operator proves to be eligible for
// the group generated by that seed. This is an interactive on-chain process,
// and joinDKGIfEligible can block for an extended period of time while it
// completes the on-chain operation.
func (n *node) joinDKGIfEligible(seed *big.Int, startBlock uint64) {
n.dkgExecutor.executeDkgIfEligible(seed, startBlock)
// completes the on-chain operation. The execution can be delayed by an
// arbitrary number of blocks using the delayBlocks argument. This allows
// confirming the state on-chain - e.g. wait for the required number of
// confirming blocks - before executing the off-chain action.
func (n *node) joinDKGIfEligible(
seed *big.Int,
startBlock uint64,
delayBlocks uint64,
) {
n.dkgExecutor.executeDkgIfEligible(seed, startBlock, delayBlocks)
}

// validateDKG performs the submitted DKG result validation process.
Expand Down
79 changes: 70 additions & 9 deletions pkg/tbtc/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,25 +137,86 @@ func Initialize(
if ok := deduplicator.notifyDKGStarted(
event.Seed,
); !ok {
logger.Warnf(
"DKG started event with seed [0x%x] and "+
"starting block [%v] has been already processed",
logger.Infof(
"DKG started event with seed [0x%x] has been "+
"already processed",
event.Seed,
event.BlockNumber,
)
return
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
}

confirmationBlock := event.BlockNumber + dkgStartedConfirmationBlocks

logger.Infof(
"DKG started with seed [0x%x] at block [%v]",
"observed DKG started event with seed [0x%x] and "+
"starting block [%v]; waiting for block [%v] to confirm",
event.Seed,
event.BlockNumber,
confirmationBlock,
)

node.joinDKGIfEligible(
event.Seed,
event.BlockNumber,
)
err := node.waitForBlockHeight(ctx, confirmationBlock)
if err != nil {
logger.Errorf("failed to confirm DKG started event: [%v]", err)
return
}

dkgState, err := chain.GetDKGState()
if err != nil {
logger.Errorf("failed to check DKG state: [%v]", err)
return
}

if dkgState == AwaitingResult {
// Fetch all past DKG started events starting from one
// confirmation period before the original event's block.
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
// If there was a chain reorg, the event we received could be
// moved to a block with a lower number than the one
// we received.
pastEvents, err := chain.PastDKGStartedEvents(
&DKGStartedEventFilter{
StartBlock: event.BlockNumber - dkgStartedConfirmationBlocks,
},
)
if err != nil {
logger.Errorf("failed to get past DKG started events: [%v]", err)
return
}

// Should not happen but just in case.
if len(pastEvents) == 0 {
logger.Errorf("no past DKG started events")
return
}

lastEvent := pastEvents[len(pastEvents)-1]
pdyraga marked this conversation as resolved.
Show resolved Hide resolved

logger.Infof(
"DKG started with seed [0x%x] at block [%v]",
lastEvent.Seed,
lastEvent.BlockNumber,
)

// The off-chain protocol should be started as close as possible
// to the current block or even further. Starting the off-chain
// protocol with a past block will likely cause a failure of the
// first attempt as the start block is used to synchronize
// the announcements and the state machine. Here we ensure
// a proper start point by delaying the execution by the
// confirmation period length.
node.joinDKGIfEligible(
lastEvent.Seed,
lastEvent.BlockNumber,
dkgStartedConfirmationBlocks,
)
} else {
logger.Infof(
"DKG started event with seed [0x%x] and starting "+
"block [%v] was not confirmed",
event.Seed,
event.BlockNumber,
)
}
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
}()
})

Expand Down