diff --git a/pkg/tbtc/chain_test.go b/pkg/tbtc/chain_test.go index 97eef9e0c8..d8ad9bd2e4 100644 --- a/pkg/tbtc/chain_test.go +++ b/pkg/tbtc/chain_test.go @@ -41,6 +41,9 @@ type localChain struct { dkgResultChallengeHandlersMutex sync.Mutex dkgResultChallengeHandlers map[int]func(submission *DKGResultChallengedEvent) + inactivityClaimedHandlersMutex sync.Mutex + inactivityClaimedHandlers map[int]func(submission *InactivityClaimedEvent) + dkgMutex sync.Mutex dkgState DKGState dkgResult *DKGChainResult @@ -49,6 +52,9 @@ type localChain struct { walletsMutex sync.Mutex wallets map[[20]byte]*WalletChainData + inactivityNonceMutex sync.Mutex + inactivityNonces map[[32]byte]uint64 + blocksByTimestampMutex sync.Mutex blocksByTimestamp map[uint64]uint64 @@ -553,9 +559,20 @@ func (lc *localChain) DKGParameters() (*DKGParameters, error) { } func (lc *localChain) OnInactivityClaimed( - func(event *InactivityClaimedEvent), + handler func(event *InactivityClaimedEvent), ) subscription.EventSubscription { - panic("unsupported") + lc.inactivityClaimedHandlersMutex.Lock() + defer lc.inactivityClaimedHandlersMutex.Unlock() + + handlerID := generateHandlerID() + lc.inactivityClaimedHandlers[handlerID] = handler + + return subscription.NewEventSubscription(func() { + lc.inactivityClaimedHandlersMutex.Lock() + defer lc.inactivityClaimedHandlersMutex.Unlock() + + delete(lc.inactivityClaimedHandlers, handlerID) + }) } func (lc *localChain) AssembleInactivityClaim( @@ -567,7 +584,20 @@ func (lc *localChain) AssembleInactivityClaim( *InactivityClaim, error, ) { - panic("unsupported") + signingMembersIndexes := make([]group.MemberIndex, 0) + signaturesConcatenation := make([]byte, 0) + for memberIndex, signature := range signatures { + signingMembersIndexes = append(signingMembersIndexes, memberIndex) + signaturesConcatenation = append(signaturesConcatenation, signature...) + } + + return &InactivityClaim{ + WalletID: walletID, + InactiveMembersIndices: inactiveMembersIndices, + HeartbeatFailed: heartbeatFailed, + Signatures: signaturesConcatenation, + SigningMembersIndices: signingMembersIndexes, + }, nil } func (lc *localChain) SubmitInactivityClaim( @@ -575,7 +605,33 @@ func (lc *localChain) SubmitInactivityClaim( nonce *big.Int, groupMembers []uint32, ) error { - panic("unsupported") + lc.inactivityClaimedHandlersMutex.Lock() + defer lc.inactivityClaimedHandlersMutex.Unlock() + + lc.inactivityNonceMutex.Lock() + defer lc.inactivityNonceMutex.Unlock() + + if nonce.Uint64() != lc.inactivityNonces[claim.WalletID] { + return fmt.Errorf("wrong inactivity claim nonce") + } + + blockNumber, err := lc.blockCounter.CurrentBlock() + if err != nil { + return fmt.Errorf("failed to get the current block") + } + + for _, handler := range lc.inactivityClaimedHandlers { + handler(&InactivityClaimedEvent{ + WalletID: claim.WalletID, + Nonce: nonce, + Notifier: "", + BlockNumber: blockNumber, + }) + } + + lc.inactivityNonces[claim.WalletID]++ + + return nil } func (lc *localChain) CalculateInactivityClaimHash( @@ -598,7 +654,11 @@ func (lc *localChain) CalculateInactivityClaimHash( } func (lc *localChain) GetInactivityClaimNonce(walletID [32]byte) (*big.Int, error) { - panic("unsupported") + lc.inactivityNonceMutex.Lock() + defer lc.inactivityNonceMutex.Unlock() + + nonce := lc.inactivityNonces[walletID] + return big.NewInt(int64(nonce)), nil } func (lc *localChain) PastDepositRevealedEvents( @@ -1182,7 +1242,11 @@ func ConnectWithKey( dkgResultChallengeHandlers: make( map[int]func(submission *DKGResultChallengedEvent), ), + inactivityClaimedHandlers: make( + map[int]func(submission *InactivityClaimedEvent), + ), wallets: make(map[[20]byte]*WalletChainData), + inactivityNonces: make(map[[32]byte]uint64), blocksByTimestamp: make(map[uint64]uint64), blocksHashesByNumber: make(map[uint64][32]byte), pastDepositRevealedEvents: make(map[[32]byte][]*DepositRevealedEvent), diff --git a/pkg/tbtc/inactivity_test.go b/pkg/tbtc/inactivity_test.go index d30bccde24..e9c4937676 100644 --- a/pkg/tbtc/inactivity_test.go +++ b/pkg/tbtc/inactivity_test.go @@ -1,6 +1,7 @@ package tbtc import ( + "context" "fmt" "math/big" "reflect" @@ -8,12 +9,130 @@ import ( "golang.org/x/crypto/sha3" + "github.com/keep-network/keep-core/pkg/bitcoin" + "github.com/keep-network/keep-core/pkg/chain" + "github.com/keep-network/keep-core/pkg/chain/local_v1" + "github.com/keep-network/keep-core/pkg/generator" "github.com/keep-network/keep-core/pkg/internal/tecdsatest" + "github.com/keep-network/keep-core/pkg/net/local" + "github.com/keep-network/keep-core/pkg/operator" "github.com/keep-network/keep-core/pkg/protocol/group" "github.com/keep-network/keep-core/pkg/protocol/inactivity" "github.com/keep-network/keep-core/pkg/tecdsa" ) +func TestInactivityClaimExecutor_ClaimInactivity(t *testing.T) { + executor := setupInactivityClaimExecutor(t) + + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + + message := big.NewInt(100) + + inactiveMembersIndexes := []group.MemberIndex{1, 4} + + err := executor.claimInactivity( + ctx, + inactiveMembersIndexes, + true, + message, + ) + if err != nil { + t.Fatal(err) + } +} + +func setupInactivityClaimExecutor(t *testing.T) *inactivityClaimExecutor { + groupParameters := &GroupParameters{ + GroupSize: 5, + GroupQuorum: 4, + HonestThreshold: 3, + } + + operatorPrivateKey, operatorPublicKey, err := operator.GenerateKeyPair( + local_v1.DefaultCurve, + ) + if err != nil { + t.Fatal(err) + } + + localChain := ConnectWithKey(operatorPrivateKey) + + localProvider := local.ConnectWithKey(operatorPublicKey) + + operatorAddress, err := localChain.Signing().PublicKeyToAddress( + operatorPublicKey, + ) + if err != nil { + t.Fatal(err) + } + + var operators []chain.Address + for i := 0; i < groupParameters.GroupSize; i++ { + operators = append(operators, operatorAddress) + } + + testData, err := tecdsatest.LoadPrivateKeyShareTestFixtures( + groupParameters.GroupSize, + ) + if err != nil { + t.Fatalf("failed to load test data: [%v]", err) + } + + signers := make([]*signer, len(testData)) + for i := range testData { + privateKeyShare := tecdsa.NewPrivateKeyShare(testData[i]) + + signers[i] = &signer{ + wallet: wallet{ + publicKey: privateKeyShare.PublicKey(), + signingGroupOperators: operators, + }, + signingGroupMemberIndex: group.MemberIndex(i + 1), + privateKeyShare: privateKeyShare, + } + } + + keyStorePersistence := createMockKeyStorePersistence(t, signers...) + + walletPublicKeyHash := bitcoin.PublicKeyHash(signers[0].wallet.publicKey) + ecdsaWalletID := [32]byte{1, 2, 3} + + localChain.setWallet( + walletPublicKeyHash, + &WalletChainData{ + EcdsaWalletID: ecdsaWalletID, + }, + ) + + node, err := newNode( + groupParameters, + localChain, + newLocalBitcoinChain(), + localProvider, + keyStorePersistence, + &mockPersistenceHandle{}, + generator.StartScheduler(), + &mockCoordinationProposalGenerator{}, + Config{}, + ) + if err != nil { + t.Fatal(err) + } + + executor, ok, err := node.getInactivityClaimExecutor( + signers[0].wallet.publicKey, + ) + if err != nil { + t.Fatal(err) + } + if !ok { + t.Fatal("node is supposed to control wallet signers") + } + + return executor +} + func TestSignClaim_SigningSuccessful(t *testing.T) { chain := Connect() inactivityClaimSigner := newInactivityClaimSigner(chain)