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 42 commits
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
350 changes: 27 additions & 323 deletions pkg/chain/ethereum/beacon.go

Large diffs are not rendered by default.

815 changes: 587 additions & 228 deletions pkg/chain/ethereum/tbtc.go

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions pkg/chain/ethereum/tbtc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
package ethereum

import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"math/big"
"reflect"
"testing"

"github.com/keep-network/keep-core/pkg/chain"

"github.com/ethereum/go-ethereum/common"
"github.com/keep-network/keep-core/pkg/internal/testutils"
"github.com/keep-network/keep-core/pkg/protocol/group"
)

func TestComputeOperatorsIDsHash(t *testing.T) {
operatorIDs := []chain.OperatorID{
5, 1, 55, 45435534, 33, 345, 23, 235, 3333, 2,
}

hash, err := computeOperatorsIDsHash(operatorIDs)
if err != nil {
t.Fatal(err)
}

expectedHash := "8cd41effd4ee91b56d6b2f836efdcac11ab1ef2ae228e348814d0e6c2966d01e"

testutils.AssertStringsEqual(
t,
"hash",
expectedHash,
hex.EncodeToString(hash[:]),
)
}

func TestConvertSignaturesToChainFormat(t *testing.T) {
signatureSize := 65

signature1 := common.LeftPadBytes([]byte{1, 2, 3}, signatureSize)
signature2 := common.LeftPadBytes([]byte{4, 5, 6}, signatureSize)
signature3 := common.LeftPadBytes([]byte{7}, signatureSize)
signature4 := common.LeftPadBytes([]byte{8, 9, 10}, signatureSize)
signature5 := common.LeftPadBytes([]byte{11, 12, 13}, signatureSize)

invalidSignature := common.LeftPadBytes([]byte("invalid"), signatureSize-1)

var tests = map[string]struct {
signaturesMap map[group.MemberIndex][]byte
expectedIndices []group.MemberIndex
expectedError error
}{
"one valid signature": {
signaturesMap: map[uint8][]byte{
1: signature1,
},
expectedIndices: []group.MemberIndex{1},
},
"five valid signatures": {
signaturesMap: map[group.MemberIndex][]byte{
3: signature3,
1: signature1,
4: signature4,
5: signature5,
2: signature2,
},
expectedIndices: []group.MemberIndex{1, 2, 3, 4, 5},
},
"invalid signature": {
signaturesMap: map[group.MemberIndex][]byte{
1: signature1,
2: invalidSignature,
},
expectedError: fmt.Errorf("invalid signature size for member [2] got [64] bytes but [65] bytes required"),
},
}
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
indicesSlice, signaturesSlice, err :=
convertSignaturesToChainFormat(test.signaturesMap)

if !reflect.DeepEqual(err, test.expectedError) {
t.Errorf(
"unexpected error\nexpected: [%v]\nactual: [%v]\n",
test.expectedError,
err,
)
}

if test.expectedError == nil {
if !reflect.DeepEqual(test.expectedIndices, indicesSlice) {
t.Errorf(
"unexpected indices\n"+
"expected: [%v]\n"+
"actual: [%v]\n",
test.expectedIndices,
indicesSlice,
)
}

testutils.AssertIntsEqual(
t,
"signatures slice length",
signatureSize*len(test.signaturesMap),
len(signaturesSlice),
)
}

for i, memberIndex := range indicesSlice {
actualSignature := signaturesSlice[signatureSize*i : signatureSize*(i+1)]
if !bytes.Equal(
test.signaturesMap[memberIndex],
actualSignature,
) {
t.Errorf(
"invalid signatures for member %v\nexpected: %v\nactual: %v\n",
memberIndex,
test.signaturesMap[memberIndex],
actualSignature,
)
}
}
})
}
}

func TestConvertPubKeyToChainFormat(t *testing.T) {
bytes30 := []byte{229, 19, 136, 216, 125, 157, 135, 142, 67, 130,
136, 13, 76, 188, 32, 218, 243, 134, 95, 73, 155, 24, 38, 73, 117, 90,
215, 95, 216, 19}
bytes31 := []byte{182, 142, 176, 51, 131, 130, 111, 197, 191, 103, 180, 137,
171, 101, 34, 78, 251, 234, 118, 184, 16, 116, 238, 82, 131, 153, 134,
17, 46, 158, 94}

expectedResult := [64]byte{
// padding
00, 00,
// bytes30
229, 19, 136, 216, 125, 157, 135, 142, 67, 130, 136, 13, 76, 188, 32,
218, 243, 134, 95, 73, 155, 24, 38, 73, 117, 90, 215, 95, 216, 19,
// padding
00,
// bytes31
182, 142, 176, 51, 131, 130, 111, 197, 191, 103, 180, 137, 171, 101, 34,
78, 251, 234, 118, 184, 16, 116, 238, 82, 131, 153, 134, 17, 46, 158, 94,
}

actualResult, err := convertPubKeyToChainFormat(
&ecdsa.PublicKey{
X: new(big.Int).SetBytes(bytes30),
Y: new(big.Int).SetBytes(bytes31),
},
)

if err != nil {
t.Errorf("unexpected error [%v]", err)
}

testutils.AssertBytesEqual(
t,
expectedResult[:],
actualResult[:],
)
}

func TestValidateMemberIndex(t *testing.T) {
one := big.NewInt(1)
maxMemberIndex := big.NewInt(255)

var tests = map[string]struct {
chainMemberIndex *big.Int
expectedError error
}{
"less than max member index": {
chainMemberIndex: new(big.Int).Sub(maxMemberIndex, one),
expectedError: nil,
},
"max member index": {
chainMemberIndex: maxMemberIndex,
expectedError: nil,
},
"greater than max member index": {
chainMemberIndex: new(big.Int).Add(maxMemberIndex, one),
expectedError: fmt.Errorf("invalid member index value: [256]"),
},
}

for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
err := validateMemberIndex(test.chainMemberIndex)

if !reflect.DeepEqual(err, test.expectedError) {
t.Errorf(
"unexpected error\nexpected: [%v]\nactual: [%v]\n",
test.expectedError,
err,
)
}
})
}
}

func TestCalculateDKGResultSignatureHash(t *testing.T) {
chainID := big.NewInt(1)

groupPublicKey, err := hex.DecodeString(
"989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9d" +
"218b65e7d91c752f7b22eaceb771a9af3a6f3d3f010a5d471a1aeef7d7713af",
)
if err != nil {
t.Fatal(err)
}

misbehavedMembersIndexes := []group.MemberIndex{2, 55}

startBlock := big.NewInt(2000)

hash, err := calculateDKGResultSignatureHash(
chainID,
groupPublicKey,
misbehavedMembersIndexes,
startBlock,
)
if err != nil {
t.Fatal(err)
}

expectedHash := "25f917154586c2be0b6364f5c4758580e535bc01ed4881211000c9267aef3a3b"

testutils.AssertStringsEqual(
t,
"hash",
expectedHash,
hex.EncodeToString(hash[:]),
)
}

func TestParseDkgResultValidationOutcome(t *testing.T) {
isValid, err := parseDkgResultValidationOutcome(
&struct {
bool
string
}{
true,
"",
},
)
if err != nil {
t.Fatal(err)
}
testutils.AssertBoolsEqual(t, "validation outcome", true, isValid)

isValid, err = parseDkgResultValidationOutcome(
&struct {
bool
string
}{
false,
"",
},
)
if err != nil {
t.Fatal(err)
}
testutils.AssertBoolsEqual(t, "validation outcome", false, isValid)

_, err = parseDkgResultValidationOutcome(
struct {
bool
string
}{
true,
"",
},
)
expectedErr := fmt.Errorf("result validation outcome is not a pointer")
if !reflect.DeepEqual(expectedErr, err) {
t.Errorf(
"unexpected error\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedErr,
err,
)
}

_, err = parseDkgResultValidationOutcome(
&struct {
string
bool
}{
"",
true,
},
)
expectedErr = fmt.Errorf("cannot parse result validation outcome")
if !reflect.DeepEqual(expectedErr, err) {
t.Errorf(
"unexpected error\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedErr,
err,
)
}
}
6 changes: 6 additions & 0 deletions pkg/chain/local_v1/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,12 @@ func (c *localChain) IsBetaOperator() (bool, error) {
panic("unsupported")
}

func (c *localChain) GetOperatorID(
operatorAddress chain.Address,
) (chain.OperatorID, error) {
panic("unsupported")
}

func GenerateHandlerID() int {
// #nosec G404 (insecure random number source (rand))
// Local chain implementation doesn't require secure randomness.
Expand Down
15 changes: 15 additions & 0 deletions pkg/chain/operator_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package chain

import "math"

// OperatorID is a unique identifier of an operator assigned by the Sortition
// Pool when the operator enters the pool for the first time. ID is never
// changing for the given operator address.
type OperatorID = uint32
pdyraga marked this conversation as resolved.
Show resolved Hide resolved

// MaxOperatorID is the maximum allowed value for OperatorID supported by
// Sortition Pool contract.
const MaxOperatorID = math.MaxUint32

// OperatorIDs is a list of OperatorID values.
type OperatorIDs []OperatorID
3 changes: 3 additions & 0 deletions pkg/sortition/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,7 @@ type Chain interface {
// Returns true if operator is a beta operator, false otherwise.
// Chaosnet status does not matter.
IsBetaOperator() (bool, error)

// GetOperatorID returns the operator ID for the given operator address.
GetOperatorID(operatorAddress chain.Address) (chain.OperatorID, error)
}
6 changes: 6 additions & 0 deletions pkg/sortition/internal/local/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ func (c *Chain) IsBetaOperator() (bool, error) {
return c.isBetaOperator, nil
}

func (c *Chain) GetOperatorID(
operatorAddress chain.Address,
) (chain.OperatorID, error) {
panic("unsupported")
}

func (c *Chain) SetCurrentTimestamp(currentTimestamp *big.Int) {
c.currentTimestamp = currentTimestamp
}
Expand Down
Loading