diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be8df8..ec8d53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased +### Bug fix + +- [#53](https://github.com/babylonlabs-io/btc-staker/pull/53) Use only quorum of +signatures when building unbonding transaction witness + ## v0.7.0 ### Api breaking diff --git a/babylonclient/babyloncontroller.go b/babylonclient/babyloncontroller.go index 69414e5..5b1522b 100644 --- a/babylonclient/babyloncontroller.go +++ b/babylonclient/babyloncontroller.go @@ -842,14 +842,13 @@ func (bc *BabylonController) IsTxAlreadyPartOfDelegation(stakingTxHash *chainhas // Test methods for e2e testing // Different babylon sig methods to support e2e testing -func (bc *BabylonController) SubmitCovenantSig( +func (bc *BabylonController) CreateCovenantMessage( covPubKey *bbntypes.BIP340PubKey, stakingTxHash string, slashStakingAdaptorSigs [][]byte, unbondindgSig *bbntypes.BIP340Signature, slashUnbondingAdaptorSigs [][]byte, - -) (*pv.RelayerTxResponse, error) { +) *btcstypes.MsgAddCovenantSigs { msg := &btcstypes.MsgAddCovenantSigs{ Signer: bc.getTxSigner(), Pk: covPubKey, @@ -859,7 +858,19 @@ func (bc *BabylonController) SubmitCovenantSig( SlashingUnbondingTxSigs: slashUnbondingAdaptorSigs, } - return bc.reliablySendMsgs([]sdk.Msg{msg}) + return msg +} + +func (bc *BabylonController) SubmitMultipleCovenantMessages( + covenantMsgs []*btcstypes.MsgAddCovenantSigs, +) (*pv.RelayerTxResponse, error) { + var msgs []sdk.Msg + + for _, covenantMsg := range covenantMsgs { + msgs = append(msgs, covenantMsg) + } + + return bc.reliablySendMsgs(msgs) } func (bc *BabylonController) QueryPendingBTCDelegations() ([]*btcstypes.BTCDelegationResponse, error) { diff --git a/itest/e2e_test.go b/itest/e2e_test.go index ebc8c14..2a98f33 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -435,7 +435,13 @@ func retrieveTransactionFromMempool(t *testing.T, client *rpcclient.Client, hash var txes []*btcutil.Tx for _, txHash := range hashes { tx, err := client.GetRawTransaction(txHash) - require.NoError(t, err) + + if err != nil { + // this is e2e helper method, so this error most probably some of the + // transactions are still not in the mempool + return []*btcutil.Tx{} + } + txes = append(txes, tx) } return txes @@ -1026,7 +1032,10 @@ func (tm *TestManager) insertAllMinedBlocksToBabylon(t *testing.T) { require.NoError(t, err) } -func (tm *TestManager) insertCovenantSigForDelegation(t *testing.T, btcDel *btcstypes.BTCDelegationResponse) { +func (tm *TestManager) insertCovenantSigForDelegation( + t *testing.T, + btcDel *btcstypes.BTCDelegationResponse, +) { fpBTCPKs, err := bbntypes.NewBTCPKsFromBIP340PKs(btcDel.FpBtcPkList) require.NoError(t, err) @@ -1101,16 +1110,23 @@ func (tm *TestManager) insertCovenantSigForDelegation(t *testing.T, btcDel *btcs ) require.NoError(t, err) + var messages []*btcstypes.MsgAddCovenantSigs for i := 0; i < len(tm.CovenantPrivKeys); i++ { - _, err = tm.BabylonClient.SubmitCovenantSig( + msg := tm.BabylonClient.CreateCovenantMessage( bbntypes.NewBIP340PubKeyFromBTCPK(tm.CovenantPrivKeys[i].PubKey()), stakingMsgTx.TxHash().String(), covenantSlashingTxSigs[i].AdaptorSigs, bbntypes.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), covenantUnbondingSlashingTxSigs[i].AdaptorSigs, ) - require.NoError(t, err) + messages = append(messages, msg) } + // we insert are covenant signatures in on message, this way staker + // program must handle the case of all signatures being present in Babylon + // delegation + // it also speeds up the tests + _, err = tm.BabylonClient.SubmitMultipleCovenantMessages(messages) + require.NoError(t, err) } func TestStakingFailures(t *testing.T) { diff --git a/staker/stakerapp.go b/staker/stakerapp.go index ff6dcbd..618e069 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1039,11 +1039,19 @@ func (app *StakerApp) sendUnbondingTxToBtcWithWitness( return fmt.Errorf("failed to receive stakerUnbondingSig.Signature") } - covenantSigantures := createWitnessSignaturesForPubKeys( + covenantSigantures, err := createWitnessSignaturesForPubKeys( params.CovenantPks, + params.CovenantQuruomThreshold, unbondingData.CovenantSignatures, ) + if err != nil { + app.logger.WithFields(logrus.Fields{ + "stakingTxHash": stakingTxHash, + "err": err, + }).Fatalf("failed to create witness to send unbonding tx") + } + witness, err := unbondingSpendInfo.CreateUnbondingPathWitness( covenantSigantures, stakerUnbondingSig.Signature, diff --git a/staker/types.go b/staker/types.go index 3bab322..4a2e803 100644 --- a/staker/types.go +++ b/staker/types.go @@ -75,13 +75,24 @@ func pubKeyToString(pubKey *btcec.PublicKey) string { func createWitnessSignaturesForPubKeys( covenantPubKeys []*btcec.PublicKey, + covenantQuorum uint32, receivedSignaturePairs []stakerdb.PubKeySigPair, -) []*schnorr.Signature { +) ([]*schnorr.Signature, error) { + + if len(receivedSignaturePairs) < int(covenantQuorum) { + return nil, fmt.Errorf("not enough signatures to create witness. Required: %d, received: %d", covenantQuorum, len(receivedSignaturePairs)) + } + // create map of received signatures - receivedSignatures := make(map[string]*schnorr.Signature) + receivedSignaturesUpToQuorum := make(map[string]*schnorr.Signature) for _, pair := range receivedSignaturePairs { - receivedSignatures[pubKeyToString(pair.PubKey)] = pair.Signature + // we are only interested in quorum number of signatures + if len(receivedSignaturesUpToQuorum) >= int(covenantQuorum) { + break + } + + receivedSignaturesUpToQuorum[pubKeyToString(pair.PubKey)] = pair.Signature } sortedPubKeys := sortPubKeysForWitness(covenantPubKeys) @@ -91,12 +102,12 @@ func createWitnessSignaturesForPubKeys( for i, key := range sortedPubKeys { k := key - if signature, found := receivedSignatures[pubKeyToString(k)]; found { + if signature, found := receivedSignaturesUpToQuorum[pubKeyToString(k)]; found { signatures[i] = signature } } - return signatures + return signatures, nil } func slashingTxForStakingTx( @@ -424,7 +435,7 @@ func buildUnbondingSpendInfo( return nil, fmt.Errorf("cannot create witness for sending unbonding tx. Unbonding data does not contain unbonding transaction") } - if len(unbondingData.CovenantSignatures) != int(params.CovenantQuruomThreshold) { + if len(unbondingData.CovenantSignatures) < int(params.CovenantQuruomThreshold) { return nil, fmt.Errorf("cannot create witness for sending unbonding tx. Unbonding data does not contain all necessary signatures. Required: %d, received: %d", params.CovenantQuruomThreshold, len(unbondingData.CovenantSignatures)) }