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

DKG saga, part 4: On-chain integration for TBTC DKG #3427

Merged
merged 52 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
91b0092
Disabled mock random beacon functionality
pdyraga Dec 2, 2022
93a7b8a
Convert signatures to chain format
pdyraga Dec 2, 2022
0c671c7
Convert public key to chain format
pdyraga Dec 2, 2022
6bfe7ad
TbtcChain OnDKGStarted replaced with a real implementation
pdyraga Dec 2, 2022
f09b904
TbtcChain OnDKGResultSubmitted replaced with a real implementation
pdyraga Dec 2, 2022
882ba38
TbtcChain GetDKGState replaced with a real implementation
pdyraga Dec 2, 2022
67b3a70
Return operator IDs next to operator addresses for ECDSA group selection
pdyraga Dec 2, 2022
eab2ec2
Half-baked implementation of TbtcChain SubmitDKGResult
pdyraga Dec 2, 2022
1539b48
Complete `SubmitDKGResult` implementation
lukasz-zimnoch Dec 7, 2022
169a8d4
Temporary heartbeat mechanism
lukasz-zimnoch Dec 7, 2022
622e8a9
Calculate the DKG result hash in the format expected by the on-chain …
lukasz-zimnoch Dec 8, 2022
3fa8fba
Support for DKG result challenges and approvals
lukasz-zimnoch Dec 9, 2022
138775f
Merge branch 'main' into dkg-part-3
lukasz-zimnoch Dec 9, 2022
bf678e9
Cover more paths in the `TestDkgExecutor_ExecuteDkgValidation` test
lukasz-zimnoch Dec 12, 2022
d65e3c5
Rework DKG result submitter
lukasz-zimnoch Dec 12, 2022
8d48b64
Remove DKG stop pill mechanism
lukasz-zimnoch Dec 12, 2022
dd172b5
Final rework of the DKG stop signals
lukasz-zimnoch Dec 12, 2022
b81ae58
Pass validation outcome as a pointer
lukasz-zimnoch Dec 13, 2022
5d252d7
Simplify DKG context management
lukasz-zimnoch Dec 13, 2022
0cb6d2e
Merge branch 'main' into dkg-part-3
lukasz-zimnoch Dec 13, 2022
da7d0f7
Sort the signing member indices during DKG result submission
lukasz-zimnoch Dec 14, 2022
28e7d58
Do not prefix the Ethereum DKG result hash
lukasz-zimnoch Dec 15, 2022
c1c920f
Compute DKG result hash using an unprefixed group public key
lukasz-zimnoch Dec 15, 2022
47a5ea1
Improve logs on result challenge and approval
lukasz-zimnoch Dec 16, 2022
0e55626
Add a TODO about actions that should be done after a DKG result is ch…
lukasz-zimnoch Dec 16, 2022
3f6dc83
Improve logging of the "non-eligible for DKG result approval" path
lukasz-zimnoch Dec 16, 2022
9904287
Make formatter happy
lukasz-zimnoch Dec 16, 2022
8f30947
Add missing `bridge` assignment
lukasz-zimnoch Dec 16, 2022
7c58a6b
Fail-fast if the chain returns 0 as operator ID
lukasz-zimnoch Dec 16, 2022
bfd5cfb
Change the way how node's operator address and ID are determined
lukasz-zimnoch Dec 19, 2022
0a8eef3
Document `GetOperatorID` Ethereum implementation
lukasz-zimnoch Dec 20, 2022
18429dc
Additional validation within `SelectGroup`
lukasz-zimnoch Dec 20, 2022
436b114
Clarify the need of sorted indexes in the `convertSignaturesToChainFo…
lukasz-zimnoch Dec 20, 2022
07d4b58
Refactor the code responsible for dealing with DKG result hashes
lukasz-zimnoch Dec 20, 2022
cb19259
Additional improvements to `CalculateDKGResultSignatureHash`
lukasz-zimnoch Dec 20, 2022
fdbd05f
Refactoring of the `tbtc.Chain` interface
lukasz-zimnoch Dec 20, 2022
d78aa84
Remove `operatorsIDs` from the `tbtc.localChain` component
lukasz-zimnoch Dec 20, 2022
4a8edfa
Introduce `generateHandlerID` to calculate IDs without relying on `lo…
lukasz-zimnoch Dec 20, 2022
ca63930
Remove redundant fields from `dkgRetryLoop`
lukasz-zimnoch Dec 20, 2022
d659537
Make the DKG retry loop responsible for computing the attempt timeout
lukasz-zimnoch Dec 20, 2022
d05c5c0
Cosmetic improvement in `activeWalletPublicKey` function
lukasz-zimnoch Dec 20, 2022
9ea6adf
Remove the TODO about acting on result challenges
lukasz-zimnoch Dec 22, 2022
b70ccb8
Harden the DKG result validation logic
lukasz-zimnoch Dec 27, 2022
f9c0d1b
Increase DKG result submission and aproval delay steps
lukasz-zimnoch Dec 28, 2022
349bdfa
Remove the chain config from `tbtc.Chain` interface
lukasz-zimnoch Dec 28, 2022
f341e4e
Add TODO about updating `DKGResultSubmittedEvent` subscription
lukasz-zimnoch Dec 28, 2022
de2d182
Improve docs of `finalSigningGroup`
lukasz-zimnoch Dec 28, 2022
a630a63
Fix docstring of `calculateDKGResultSignatureHash` function
lukasz-zimnoch Dec 28, 2022
be0aac8
Remove unnecessary check
lukasz-zimnoch Dec 28, 2022
55d01d6
Improve docs of `convertSignaturesToChainFormat`
lukasz-zimnoch Dec 28, 2022
9536de6
Simplify the `TestDkgExecutor_ExecuteDkgValidation` test
lukasz-zimnoch Dec 28, 2022
70afe95
Merge branch 'main' into dkg-part-3
lukasz-zimnoch Dec 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions pkg/tbtc/deduplicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package tbtc

import (
"encoding/hex"
"github.com/keep-network/keep-common/pkg/cache"
"math/big"
"strconv"
"time"

"github.com/keep-network/keep-common/pkg/cache"
)

const (
Expand Down Expand Up @@ -67,12 +69,16 @@ func (d *deduplicator) notifyDKGStarted(
// upon the DKG result submission. It returns boolean indicating whether the
// client should proceed with the actions or ignore the event as a duplicate.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current deduplication logic makes a lot of sense given the challenges but we should make it crystal-clear in the docs in case future-us will ever think about removing the result block from the hash. Something like:

// (...)
//
// IMPORTANT: This function considers two events with the same seed and hash as
// separate ones if they do not have the same block number. This is important in
// the context of a possible challenge - we never know how soon the next result
// is submitted after the challenge. In extreme circumstances, it may even be
// the same block as the last challenge.

func (d *deduplicator) notifyDKGResultSubmitted(
newDKGResultHash [32]byte,
newDKGResultSeed *big.Int,
newDKGResultHash DKGChainResultHash,
newDKGResultBlock uint64,
) bool {
d.dkgResultHashCache.Sweep()

// The cache key is the hexadecimal representation of the hash.
cacheKey := hex.EncodeToString(newDKGResultHash[:])
cacheKey := newDKGResultSeed.Text(16) +
hex.EncodeToString(newDKGResultHash[:]) +
strconv.Itoa(int(newDKGResultBlock))

// If the key is not in the cache, that means the result was not handled
// yet and the client should proceed with the execution.
if !d.dkgResultHashCache.Has(cacheKey) {
Expand Down
37 changes: 28 additions & 9 deletions pkg/tbtc/deduplicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package tbtc

import (
"encoding/hex"
"github.com/keep-network/keep-common/pkg/cache"
"math/big"
"testing"
"time"

"github.com/keep-network/keep-common/pkg/cache"
)

const testDKGSeedCachePeriod = 1 * time.Second
Expand Down Expand Up @@ -66,29 +67,47 @@ func TestNotifyDKGResultSubmitted(t *testing.T) {
var hash2 [32]byte
copy(hash2[:], hash2Bytes)

// Add the first hash.
canProcess := deduplicator.notifyDKGResultSubmitted(hash1)
// Add the original parameters.
canProcess := deduplicator.notifyDKGResultSubmitted(big.NewInt(100), hash1, 500)
if !canProcess {
t.Fatal("should be allowed to process")
}

// Add with different seed.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(101), hash1, 500)
if !canProcess {
t.Fatal("should be allowed to process")
}

// Add with different result hash.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(100), hash2, 500)
if !canProcess {
t.Fatal("should be allowed to process")
}

// Add with different result block.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(100), hash1, 501)
if !canProcess {
t.Fatal("should be allowed to process")
}

// Add the second hash.
canProcess = deduplicator.notifyDKGResultSubmitted(hash2)
// Add with all different parameters.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(101), hash2, 501)
if !canProcess {
t.Fatal("should be allowed to process")
}

// Add the first hash before caching period elapses.
canProcess = deduplicator.notifyDKGResultSubmitted(hash1)
// Add the original parameters before caching period elapses.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(100), hash1, 500)
if canProcess {
t.Fatal("should not be allowed to process")
}

// Wait until caching period elapses.
time.Sleep(testDKGResultHashCachePeriod)

// Add the first hash again.
canProcess = deduplicator.notifyDKGResultSubmitted(hash1)
// Add the original parameters again.
canProcess = deduplicator.notifyDKGResultSubmitted(big.NewInt(100), hash1, 500)
if !canProcess {
t.Fatal("should be allowed to process")
}
Expand Down
67 changes: 60 additions & 7 deletions pkg/tbtc/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const (
// respected by the given member to avoid all members approving the same
// DKG result at the same time.
dkgResultApprovalDelayStepBlocks = 5
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
// dkgResultChallengeConfirmationBlocks determines the block length of
// the confirmation period that is preserved after a DKG result challenge
// submission. Once the period elapses, the DKG state is checked to confirm
// the challenge was accepted successfully.
dkgResultChallengeConfirmationBlocks = 20
)

// dkgExecutor is a component responsible for the full execution of ECDSA
Expand Down Expand Up @@ -513,6 +518,8 @@ func (de *dkgExecutor) executeDkgValidation(
zap.String("resultHash", fmt.Sprintf("0x%x", resultHash)),
)

dkgLogger.Infof("starting DKG result validation")

isValid, err := de.chain.IsDKGResultValid(result)
if err != nil {
dkgLogger.Errorf("cannot validate DKG result: [%v]", err)
Expand All @@ -522,15 +529,61 @@ func (de *dkgExecutor) executeDkgValidation(
if !isValid {
dkgLogger.Infof("DKG result is invalid")

err = de.chain.ChallengeDKGResult(result)
if err != nil {
dkgLogger.Errorf("cannot challenge invalid DKG result: [%v]", err)
return
}
i := uint64(0)

dkgLogger.Infof("challenging invalid DKG result")
// Challenges are done along with DKG state confirmations. This is
// needed to handle chain reorgs that may wipe out the block holding
// the challenge transaction. The state check done upon the confirmation
// block makes sure the submitted challenge changed the DKG state
// as expected. If the DKG state was not changed, the challenge is
// re-submitted.
for {
i++

return
err = de.chain.ChallengeDKGResult(result)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we run this loop one more time, we do not know if the result is still the one most recently submitted to the chain. It could happen that the challenge was mined and another result got submitted later. Before we execute this loop again, we should check the result - probably reading it from the last event.

Captured it in a separate issue: #3462

if err != nil {
dkgLogger.Errorf(
"cannot challenge invalid DKG result: [%v]",
err,
)
return
}

confirmationBlock := submissionBlock +
(i * dkgResultChallengeConfirmationBlocks)

dkgLogger.Infof(
"challenging invalid DKG result; waiting for "+
"block [%v] to confirm DKG state",
confirmationBlock,
)

err := de.waitForBlockFn(context.Background(), confirmationBlock)
if err != nil {
dkgLogger.Errorf(
"error while waiting for challenge confirmation: [%v]",
err,
)
return
}

state, err := de.chain.GetDKGState()
if err != nil {
dkgLogger.Errorf("cannot check DKG state: [%v]", err)
return
}

if state != Challenge {
dkgLogger.Infof(
"invalid DKG result challenged successfully",
)
return
}

dkgLogger.Infof(
"invalid DKG result still not challenged; retrying",
)
Comment on lines +593 to +595
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log may not always be true. We could have another result submitted in the meantime and challenged. I think we should remove this log from here and move it to the beginning of the loop after we check that the result we are challenging is the last one submitted.

Captured it in a separate issue: #3462

}
}

dkgLogger.Infof("DKG result is valid")
Expand Down
11 changes: 8 additions & 3 deletions pkg/tbtc/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,25 @@ func Initialize(
_ = chain.OnDKGResultSubmitted(func(event *DKGResultSubmittedEvent) {
go func() {
if ok := deduplicator.notifyDKGResultSubmitted(
event.Seed,
event.ResultHash,
event.BlockNumber,
); !ok {
logger.Warnf(
"DKG result with hash [0x%x] and starting "+
"block [%v] has been already processed",
"Result with hash [0x%x] for DKG with seed [0x%x] "+
"and starting block [%v] has been already processed",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a starting block right? This is the block when the DKGResultSubmitted event was emitted, DKG start block is currently the block at which DkgStarted was emitted and may change in the future given #3456.

Should this log say submit block instead?

event.ResultHash,
event.Seed,
event.BlockNumber,
)
return
}

logger.Infof(
"DKG result with hash [0x%x] submitted at block [%v]",
"Result with hash [0x%x] for DKG with seed [0x%x] "+
"submitted at block [%v]",
event.ResultHash,
event.Seed,
event.BlockNumber,
)

Expand Down