diff --git a/pkg/tbtc/chain.go b/pkg/tbtc/chain.go index b9670bcdd7..49fdc59ed2 100644 --- a/pkg/tbtc/chain.go +++ b/pkg/tbtc/chain.go @@ -107,13 +107,6 @@ type DistributedKeyGenerationChain interface { startBlock uint64, ) (dkg.ResultSignatureHash, error) - CalculateInactivityClaimSignatureHash( - nonce *big.Int, - walletPublicKey *ecdsa.PublicKey, - inactiveMembersIndexes []group.MemberIndex, - heartbeatFailed bool, - ) (inactivity.ClaimSignatureHash, error) - // IsDKGResultValid checks whether the submitted DKG result is valid from // the on-chain contract standpoint. IsDKGResultValid(dkgResult *DKGChainResult) (bool, error) @@ -128,6 +121,18 @@ type DistributedKeyGenerationChain interface { DKGParameters() (*DKGParameters, error) } +type InactivityClaimChain interface { + // CalculateInactivityClaimSignatureHash calculates hash for the given + // inactivity claim. + CalculateInactivityClaimSignatureHash( + claim *inactivity.Claim, + ) (inactivity.ClaimSignatureHash, error) + + // GetInactivityClaimNonce returns inactivity claim nonce for the given + // wallet. + GetInactivityClaimNonce(walletID [32]byte) (*big.Int, error) +} + // DKGChainResultHash represents a hash of the DKGChainResult. The algorithm // used is specific to the chain. type DKGChainResultHash [32]byte @@ -461,6 +466,7 @@ type Chain interface { sortition.Chain GroupSelectionChain DistributedKeyGenerationChain + InactivityClaimChain BridgeChain WalletProposalValidatorChain } diff --git a/pkg/tbtc/heartbeat.go b/pkg/tbtc/heartbeat.go index 695a35c7e9..5aae33bd85 100644 --- a/pkg/tbtc/heartbeat.go +++ b/pkg/tbtc/heartbeat.go @@ -189,6 +189,9 @@ func (ha *heartbeatAction) execute() error { // The value of consecutive heartbeat failures exceeds the threshold. // Proceed with operator inactivity notification. err = ha.inactivityClaimExecutor.publishClaim( + // Leave the list empty. Some operators were inactive during the + // heartbeat because they were simply unstaking and therefore should not + // be punished. []group.MemberIndex{}, true, ) diff --git a/pkg/tbtc/inactivity.go b/pkg/tbtc/inactivity.go index 4988bedc1f..b66bcaf031 100644 --- a/pkg/tbtc/inactivity.go +++ b/pkg/tbtc/inactivity.go @@ -2,17 +2,28 @@ package tbtc import ( "context" + "fmt" "math/big" "sync" "github.com/ipfs/go-log/v2" + "golang.org/x/sync/semaphore" + + "github.com/keep-network/keep-core/pkg/bitcoin" "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" "github.com/keep-network/keep-core/pkg/tecdsa/inactivity" ) +// errInactivityExecutorBusy is an error returned when the inactivity claim +// executor cannot execute the inactivity claim due to another inactivity claim +// execution in progress. +var errInactivityExecutorBusy = fmt.Errorf("inactivity claim executor is busy") + type inactivityClaimExecutor struct { + lock *semaphore.Weighted + chain Chain signers []*signer @@ -25,6 +36,7 @@ func newInactivityClaimExecutor( signers []*signer, ) *inactivityClaimExecutor { return &inactivityClaimExecutor{ + lock: semaphore.NewWeighted(1), chain: chain, signers: signers, } @@ -34,9 +46,32 @@ func (ice *inactivityClaimExecutor) publishClaim( inactiveMembersIndexes []group.MemberIndex, heartbeatFailed bool, ) error { - // TODO: Build a claim and launch the publish function for all - // the signers. The value of `heartbeat` should be true and - // `inactiveMembersIndices` should be empty. + if lockAcquired := ice.lock.TryAcquire(1); !lockAcquired { + return errSigningExecutorBusy + } + defer ice.lock.Release(1) + + walletPublicKey := ice.wallet().publicKey + walletPublicKeyHash := bitcoin.PublicKeyHash(walletPublicKey) + + walletRegistryData, err := ice.chain.GetWallet(walletPublicKeyHash) + if err != nil { + return fmt.Errorf("could not get registry data on wallet: [%v]", err) + } + + nonce, err := ice.chain.GetInactivityClaimNonce( + walletRegistryData.EcdsaWalletID, + ) + if err != nil { + return fmt.Errorf("could not get nonce for wallet: [%v]", err) + } + + claim := &inactivity.Claim{ + Nonce: nonce, + WalletPublicKey: walletPublicKey, + InactiveMembersIndexes: inactiveMembersIndexes, + HeartbeatFailed: heartbeatFailed, + } wg := sync.WaitGroup{} wg.Add(len(ice.signers)) @@ -50,6 +85,9 @@ func (ice *inactivityClaimExecutor) publishClaim( }(currentSigner) } + // Wait until all controlled signers complete their routine. + wg.Wait() + return nil } @@ -78,3 +116,9 @@ func (ice *inactivityClaimExecutor) publish( inactivityClaim, ) } + +func (ice *inactivityClaimExecutor) wallet() wallet { + // All signers belong to one wallet. Take that wallet from the + // first signer. + return ice.signers[0].wallet +} diff --git a/pkg/tbtc/inactivity_submit.go b/pkg/tbtc/inactivity_submit.go index 1bbf70e0b8..8e7ad856a4 100644 --- a/pkg/tbtc/inactivity_submit.go +++ b/pkg/tbtc/inactivity_submit.go @@ -30,12 +30,7 @@ func (ics *inactivityClaimSigner) SignClaim(claim *inactivity.Claim) ( return nil, fmt.Errorf("result is nil") } - claimHash, err := ics.chain.CalculateInactivityClaimSignatureHash( - claim.Nonce, - claim.WalletPublicKey, - claim.InactiveMembersIndexes, - claim.HeartbeatFailed, - ) + claimHash, err := ics.chain.CalculateInactivityClaimSignatureHash(claim) if err != nil { return nil, fmt.Errorf( "inactivity claim hash calculation failed [%w]",