Skip to content

Commit

Permalink
Get coordination leader
Browse files Browse the repository at this point in the history
Here we implement the logic of designating the coordination leader according
to the specification presented in RFC 12.
  • Loading branch information
lukasz-zimnoch committed Nov 27, 2023
1 parent ff636e4 commit 9f7534b
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 7 deletions.
62 changes: 60 additions & 2 deletions pkg/tbtc/coordination.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package tbtc
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/chain"
"github.com/keep-network/keep-core/pkg/generator"
"github.com/keep-network/keep-core/pkg/net"
"github.com/keep-network/keep-core/pkg/protocol/group"
"golang.org/x/sync/semaphore"
"math/rand"
"sort"
)

const (
Expand Down Expand Up @@ -209,6 +212,7 @@ type coordinationExecutor struct {

coordinatedWallet wallet
membersIndexes []group.MemberIndex
operatorAddress chain.Address

broadcastChannel net.BroadcastChannel
membershipValidator *group.MembershipValidator
Expand All @@ -221,6 +225,7 @@ func newCoordinationExecutor(
chain Chain,
coordinatedWallet wallet,
membersIndexes []group.MemberIndex,
operatorAddress chain.Address,
broadcastChannel net.BroadcastChannel,
membershipValidator *group.MembershipValidator,
protocolLatch *generator.ProtocolLatch,
Expand All @@ -230,6 +235,7 @@ func newCoordinationExecutor(
chain: chain,
coordinatedWallet: coordinatedWallet,
membersIndexes: membersIndexes,
operatorAddress: operatorAddress,
broadcastChannel: broadcastChannel,
membershipValidator: membershipValidator,
protocolLatch: protocolLatch,
Expand Down Expand Up @@ -257,12 +263,18 @@ func (ce *coordinationExecutor) coordinate(
ce.protocolLatch.Lock()
defer ce.protocolLatch.Unlock()

coordinationSeed, err := ce.coordinationSeed(window)
seed, err := ce.coordinationSeed(window)
if err != nil {
return nil, fmt.Errorf("failed to compute coordination seed: [%v]", err)
}

fmt.Printf("coordinationSeed: %v\n", coordinationSeed)
leader := ce.coordinationLeader(seed)

if leader == ce.operatorAddress {
ce.leaderRoutine()
} else {
ce.followerRoutine()
}

// TODO: Implement the rest of the coordination procedure.
result := &coordinationResult{
Expand Down Expand Up @@ -299,3 +311,49 @@ func (ce *coordinationExecutor) coordinationSeed(
),
), nil
}

// coordinationLeader returns the address of the coordination leader for the
// given coordination seed.
func (ce *coordinationExecutor) coordinationLeader(seed [32]byte) chain.Address {
// First, take all operators backing the wallet.
allOperators := chain.Addresses(ce.coordinatedWallet.signingGroupOperators)

// Determine a list of unique operators.
uniqueOperators := make([]chain.Address, 0)
for operator := range allOperators.Set() {
uniqueOperators = append(uniqueOperators, operator)
}

// Sort the list of unique operators in ascending order.
sort.Slice(
uniqueOperators,
func(i, j int) bool {
return uniqueOperators[i] < uniqueOperators[j]
},
)

// #nosec G404 (insecure random number source (rand))
// Shuffling operators does not require secure randomness.
// Use first 8 bytes of the seed to initialize the RNG.
rng := rand.New(rand.NewSource(int64(binary.BigEndian.Uint64(seed[:8]))))

// Shuffle the list of unique operators.
rng.Shuffle(
len(uniqueOperators),
func(i, j int) {
uniqueOperators[i], uniqueOperators[j] =
uniqueOperators[j], uniqueOperators[i]
},
)

// The first operator in the shuffled list is the leader.
return uniqueOperators[0]
}

func (ce *coordinationExecutor) leaderRoutine() {
// TODO: Implement the leader routine.
}

func (ce *coordinationExecutor) followerRoutine() {
// TODO: Implement the follower routine.
}
44 changes: 44 additions & 0 deletions pkg/tbtc/coordination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tbtc
import (
"context"
"encoding/hex"
"github.com/keep-network/keep-core/pkg/chain"
"testing"
"time"

Expand Down Expand Up @@ -144,6 +145,7 @@ func TestCoordinationExecutor_CoordinationSeed(t *testing.T) {
}

executor := &coordinationExecutor{
// Set only relevant fields.
chain: localChain,
coordinatedWallet: coordinatedWallet,
}
Expand All @@ -163,3 +165,45 @@ func TestCoordinationExecutor_CoordinationSeed(t *testing.T) {
hex.EncodeToString(seed[:]),
)
}

func TestCoordinationExecutor_CoordinationLeader(t *testing.T) {
seedBytes, err := hex.DecodeString(
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
)
if err != nil {
t.Fatal(err)
}

var seed [32]byte
copy(seed[:], seedBytes)

coordinatedWallet := wallet{
// Set only relevant fields.
signingGroupOperators: []chain.Address{
"957ECF59507a6A74b8d98747f07a74De270D3CC3", // member 1
"5E14c0f27612fbfB7A6FE40b5A6Ec997fA62fc04", // member 2
"D2662604f8b4540336fBd3c1F48d7e9cdFbD079c", // member 3
"7CBD87ABC182216A7Aa0E8d19aA21abFA2511383", // member 4
"FAc73b03884d94a08a5c6c7BB12Ac0b20571F162", // member 5
"705C76445651530fe0D25eeE287b6164cE2c7216", // member 6
"7CBD87ABC182216A7Aa0E8d19aA21abFA2511383", // member 7 (same operator as member 4)
"405ad1f632b49A0617fbdc1fD427aF54BA9Bb3dd", // member 8
"7CBD87ABC182216A7Aa0E8d19aA21abFA2511383", // member 9 (same operator as member 4)
"5E14c0f27612fbfB7A6FE40b5A6Ec997fA62fc04", // member 10 (same operator as member 2)
},
}

executor := &coordinationExecutor{
// Set only relevant fields.
coordinatedWallet: coordinatedWallet,
}

leader := executor.coordinationLeader(seed)

testutils.AssertStringsEqual(
t,
"coordination leader",
"D2662604f8b4540336fBd3c1F48d7e9cdFbD079c",
leader.String(),
)
}
16 changes: 11 additions & 5 deletions pkg/tbtc/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,29 +365,35 @@ func (n *node) getCoordinationExecutor(
)
}

executorLogger.Infof(
"coordination executor created; controlling [%v] signers",
len(signers),
)

// The coordination executor does not need access to signers' key material.
// It is enough to pass only their member indexes.
membersIndexes := make([]group.MemberIndex, len(signers))
for i, s := range signers {
membersIndexes[i] = s.signingGroupMemberIndex
}

operatorAddress, err := n.operatorAddress()
if err != nil {
return nil, false, fmt.Errorf("failed to get operator address: [%v]", err)
}

executor := newCoordinationExecutor(
n.chain,
wallet,
membersIndexes,
operatorAddress,
broadcastChannel,
membershipValidator,
n.protocolLatch,
)

n.coordinationExecutors[executorKey] = executor

executorLogger.Infof(
"coordination executor created; controlling [%v] signers",
len(signers),
)

return executor, true, nil
}

Expand Down

0 comments on commit 9f7534b

Please sign in to comment.