diff --git a/CHANGELOG.md b/CHANGELOG.md index 3083feb..09c91cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased +## v0.5.0 + +### Bug fixes + +* [#23](https://github.com/babylonlabs-io/covenant-emulator/pull/23) Fix pre-approval flow + ### Improvements * [#20](https://github.com/babylonlabs-io/covenant-emulator/pull/20) Add signing behind diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index 121c1f4..46713f6 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -187,6 +187,10 @@ func (bc *BabylonController) QueryActiveDelegations(limit uint64) ([]*types.Dele return bc.queryDelegationsWithStatus(btcstakingtypes.BTCDelegationStatus_ACTIVE, limit) } +func (bc *BabylonController) QueryVerifiedDelegations(limit uint64) ([]*types.Delegation, error) { + return bc.queryDelegationsWithStatus(btcstakingtypes.BTCDelegationStatus_VERIFIED, limit) +} + // queryDelegationsWithStatus queries BTC delegations that need a Covenant signature // with the given status (either pending or unbonding) // it is only used when the program is running in Covenant mode @@ -273,6 +277,7 @@ func DelegationRespToDelegation(del *btcstakingtypes.BTCDelegationResponse) (*ty BtcPk: del.BtcPk.MustToBTCPK(), FpBtcPks: fpBtcPks, TotalSat: btcutil.Amount(del.TotalSat), + StakingTime: del.StakingTime, StartHeight: del.StartHeight, EndHeight: del.EndHeight, StakingTxHex: del.StakingTxHex, @@ -351,12 +356,18 @@ func (bc *BabylonController) CreateBTCDelegation( unbondingValue int64, unbondingSlashingTx *btcstakingtypes.BTCSlashingTx, delUnbondingSlashingSig *bbntypes.BIP340Signature, + isPreApproval bool, ) (*types.TxResponse, error) { fpBtcPks := make([]bbntypes.BIP340PubKey, 0, len(fpPks)) for _, v := range fpPks { fpBtcPks = append(fpBtcPks, *bbntypes.NewBIP340PubKeyFromBTCPK(v)) } + var inclusionProof *btcstakingtypes.InclusionProof + if !isPreApproval { + inclusionProof = btcstakingtypes.NewInclusionProof(stakingTxInfo.Key, stakingTxInfo.Proof) + } + msg := &btcstakingtypes.MsgCreateBTCDelegation{ StakerAddr: bc.mustGetTxSigner(), Pop: pop, @@ -365,7 +376,7 @@ func (bc *BabylonController) CreateBTCDelegation( StakingTime: stakingTime, StakingValue: stakingValue, StakingTx: stakingTxInfo.Transaction, - StakingTxInclusionProof: btcstakingtypes.NewInclusionProof(stakingTxInfo.Key, stakingTxInfo.Proof), + StakingTxInclusionProof: inclusionProof, SlashingTx: slashingTx, DelegatorSlashingSig: delSlashingSig, UnbondingTx: unbondingTx, diff --git a/covenant/covenant.go b/covenant/covenant.go index 01c1332..5662d4c 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -120,9 +120,9 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( // - CheckpointFinalizationTimeout unbondingTime := btcDel.UnbondingTime minUnbondingTime := params.MinimumUnbondingTime() - if uint64(unbondingTime) <= minUnbondingTime { + if uint32(unbondingTime) <= minUnbondingTime { ce.logger.Error("invalid unbonding time", - zap.Uint64("min_unbonding_time", minUnbondingTime), + zap.Uint32("min_unbonding_time", minUnbondingTime), zap.Uint16("got_unbonding_time", unbondingTime), ) continue @@ -374,7 +374,7 @@ func decodeDelegationTransactions(del *types.Delegation, params *types.StakingPa } // 2. verify the transactions - if err := btcstaking.CheckTransactions( + if err := btcstaking.CheckSlashingTxMatchFundingTx( slashingMsgTx, stakingMsgTx, del.StakingOutputIdx, @@ -404,7 +404,7 @@ func decodeUndelegationTransactions(del *types.Delegation, params *types.Staking } // 2. verify transactions - if err := btcstaking.CheckTransactions( + if err := btcstaking.CheckSlashingTxMatchFundingTx( unbondingSlashingMsgTx, unbondingMsgTx, 0, diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 3d206a3..0584268 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -67,7 +67,7 @@ func FuzzAddCovenantSig(f *testing.F) { // generate BTC delegation delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - stakingTimeBlocks := uint16(testutil.RandRange(r, int(params.MinStakingTime), int(params.MaxStakingTime))) + stakingTimeBlocks := uint32(testutil.RandRange(r, int(params.MinStakingTime), int(params.MaxStakingTime))) stakingValue := int64(testutil.RandRange(r, int(params.MinStakingValue), int(params.MaxStakingValue))) unbondingTime := uint16(params.MinimumUnbondingTime()) + 1 fpNum := datagen.RandomInt(r, 5) + 1 @@ -80,7 +80,7 @@ func FuzzAddCovenantSig(f *testing.F) { fpPks, params.CovenantPks, params.CovenantQuorum, - stakingTimeBlocks, + uint16(stakingTimeBlocks), stakingValue, params.SlashingPkScript, params.SlashingRate, @@ -88,14 +88,15 @@ func FuzzAddCovenantSig(f *testing.F) { ) stakingTxBytes, err := bbntypes.SerializeBTCTx(testInfo.StakingTx) require.NoError(t, err) - startHeight := datagen.RandomInt(r, 1000) + 100 + startHeight := uint32(datagen.RandomInt(r, 1000) + 100) stakingOutputIdx, err := bbntypes.GetOutputIdxInBTCTx(testInfo.StakingTx, testInfo.StakingInfo.StakingOutput) require.NoError(t, err) btcDel := &types.Delegation{ BtcPk: delPK, FpBtcPks: fpPks, + StakingTime: stakingTimeBlocks, StartHeight: startHeight, // not relevant here - EndHeight: startHeight + uint64(stakingTimeBlocks), + EndHeight: startHeight + stakingTimeBlocks, TotalSat: btcutil.Amount(stakingValue), UnbondingTime: unbondingTime, StakingTxHex: hex.EncodeToString(stakingTxBytes), diff --git a/go.mod b/go.mod index 39ae50b..f1684c9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.3.0 github.com/avast/retry-go/v4 v4.5.1 - github.com/babylonlabs-io/babylon v0.12.0 + github.com/babylonlabs-io/babylon v0.13.0 github.com/btcsuite/btcd v0.24.2 github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.5 @@ -16,6 +16,7 @@ require ( github.com/cosmos/cosmos-sdk v0.50.6 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/relayer/v2 v2.5.2 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/golang/mock v1.6.0 github.com/jessevdk/go-flags v1.5.0 github.com/jsternberg/zap-logfmt v1.3.0 @@ -91,7 +92,6 @@ require ( github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect diff --git a/go.sum b/go.sum index fc4ab50..f8760f3 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k= github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/babylonlabs-io/babylon v0.12.0 h1:s2OTcxpk0RzrkVBnVTfnPdJVYDSqnm/33YKPQqEzNCE= -github.com/babylonlabs-io/babylon v0.12.0/go.mod h1:ZOrTde9vs2xoqGTFw4xhupu2CMulnpywiuk0eh4kPOw= +github.com/babylonlabs-io/babylon v0.13.0 h1:h7cazmFmItePvZHEbLhDbsq2l7xN4e2AjDHRM7zDLkg= +github.com/babylonlabs-io/babylon v0.13.0/go.mod h1:cxRwVqVLoJ39FpyovTEHJLu1lwwrM1tE8davu7nRHwY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= diff --git a/itest/e2e_test.go b/itest/e2e_test.go index c89ea86..f160f4a 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -5,6 +5,7 @@ package e2etest import ( "testing" + "time" ) var ( @@ -12,20 +13,29 @@ var ( stakingAmount = int64(20000) ) -// TestCovenantEmulatorLifeCycle tests the whole life cycle of a finality-provider -// creation -> registration -> randomness commitment -> -// activation with BTC delegation and Covenant sig -> -// vote submission -> block finalization +// TestCovenantEmulatorLifeCycle tests the whole life cycle of a covenant emulator +// in two flows depending on whether the delegation is following pre-approval flow func TestCovenantEmulatorLifeCycle(t *testing.T) { tm, btcPks := StartManagerWithFinalityProvider(t, 1) defer tm.Stop(t) - // send a BTC delegation - _ = tm.InsertBTCDelegation(t, btcPks, stakingTime, stakingAmount) + // send a BTC delegation that is not following pre-approval flow + _ = tm.InsertBTCDelegation(t, btcPks, stakingTime, stakingAmount, false) // check the BTC delegation is pending _ = tm.WaitForNPendingDels(t, 1) // check the BTC delegation is active _ = tm.WaitForNActiveDels(t, 1) + + // send a BTC delegation that is following pre-approval flow + _ = tm.InsertBTCDelegation(t, btcPks, stakingTime, stakingAmount, true) + + // check the BTC delegation is pending + _ = tm.WaitForNPendingDels(t, 1) + + time.Sleep(10 * time.Second) + + // check the BTC delegation is verified + _ = tm.WaitForNVerifiedDels(t, 1) } diff --git a/itest/test_manager.go b/itest/test_manager.go index d2977f0..29ccd0c 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -228,7 +228,34 @@ func (tm *TestManager) WaitForNActiveDels(t *testing.T, n int) []*types.Delegati return dels } -func (tm *TestManager) InsertBTCDelegation(t *testing.T, fpPks []*btcec.PublicKey, stakingTime uint16, stakingAmount int64) *TestDelegationData { +func (tm *TestManager) WaitForNVerifiedDels(t *testing.T, n int) []*types.Delegation { + var ( + dels []*types.Delegation + err error + ) + require.Eventually(t, func() bool { + dels, err = tm.CovBBNClient.QueryVerifiedDelegations( + tm.CovenanConfig.DelegationLimit, + ) + if err != nil { + return false + } + return len(dels) == n + }, eventuallyWaitTimeOut, eventuallyPollTime) + + t.Logf("delegations are verified") + + return dels +} + +// InsertBTCDelegation inserts a BTC delegation to Babylon +// isPreApproval indicates whether the delegation follows +// pre-approval flow, if so, the inclusion proof is nil +func (tm *TestManager) InsertBTCDelegation( + t *testing.T, + fpPks []*btcec.PublicKey, stakingTime uint16, stakingAmount int64, + isPreApproval bool, +) *TestDelegationData { r := rand.New(rand.NewSource(time.Now().UnixNano())) params := tm.StakingParams @@ -284,14 +311,14 @@ func (tm *TestManager) InsertBTCDelegation(t *testing.T, fpPks []*btcec.PublicKe require.NoError(t, err) txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, blockWithStakingTx.SpvProof.MerkleNodes) - slashignSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) // delegator sig delegatorSig, err := testStakingInfo.SlashingTx.Sign( testStakingInfo.StakingTx, 1, - slashignSpendInfo.GetPkScriptPath(), + slashingSpendInfo.GetPkScriptPath(), delBtcPrivKey, ) require.NoError(t, err) @@ -345,7 +372,8 @@ func (tm *TestManager) InsertBTCDelegation(t *testing.T, fpPks []*btcec.PublicKe uint32(unbondingTime), unbondingValue, testUnbondingInfo.SlashingTx, - unbondingSig) + unbondingSig, + isPreApproval) require.NoError(t, err) t.Log("successfully submitted a BTC delegation") diff --git a/tools/go.mod b/tools/go.mod index 3c25ee4..f36d245 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.21 toolchain go1.21.4 -require github.com/babylonlabs-io/babylon v0.12.0 +require github.com/babylonlabs-io/babylon v0.13.0 require ( cloud.google.com/go v0.112.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 497d896..a2fb742 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -268,8 +268,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k= github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/babylonlabs-io/babylon v0.12.0 h1:s2OTcxpk0RzrkVBnVTfnPdJVYDSqnm/33YKPQqEzNCE= -github.com/babylonlabs-io/babylon v0.12.0/go.mod h1:ZOrTde9vs2xoqGTFw4xhupu2CMulnpywiuk0eh4kPOw= +github.com/babylonlabs-io/babylon v0.13.0 h1:h7cazmFmItePvZHEbLhDbsq2l7xN4e2AjDHRM7zDLkg= +github.com/babylonlabs-io/babylon v0.13.0/go.mod h1:cxRwVqVLoJ39FpyovTEHJLu1lwwrM1tE8davu7nRHwY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= diff --git a/types/delegation.go b/types/delegation.go index 92fa73c..879b6c9 100644 --- a/types/delegation.go +++ b/types/delegation.go @@ -1,9 +1,10 @@ package types import ( - "github.com/btcsuite/btcd/btcutil" "math" + "github.com/btcsuite/btcd/btcutil" + bbn "github.com/babylonlabs-io/babylon/types" "github.com/btcsuite/btcd/btcec/v2" @@ -16,12 +17,14 @@ type Delegation struct { // The Bitcoin secp256k1 PKs of the finality providers that // this BTC delegation delegates to FpBtcPks []*btcec.PublicKey + // The number of blocks for which the delegation is locked on BTC chain + StakingTime uint32 // The start BTC height of the BTC delegation // it is the start BTC height of the timelock - StartHeight uint64 + StartHeight uint32 // The end height of the BTC delegation // it is the end BTC height of the timelock - w - EndHeight uint64 + EndHeight uint32 // The total amount of BTC stakes in this delegation // quantified in satoshi TotalSat btcutil.Amount @@ -52,14 +55,12 @@ func (d *Delegation) HasCovenantQuorum(quorum uint32) bool { } func (d *Delegation) GetStakingTime() uint16 { - diff := d.EndHeight - d.StartHeight - - if diff > math.MaxUint16 { + if d.StakingTime > math.MaxUint16 { // In a valid delegation, EndHeight is always greater than StartHeight and it is always uint16 value panic("invalid delegation in database") } - return uint16(diff) + return uint16(d.StakingTime) } // Undelegation signalizes that the delegation is being undelegated diff --git a/types/params.go b/types/params.go index 252c4c7..a8d9bd1 100644 --- a/types/params.go +++ b/types/params.go @@ -8,9 +8,9 @@ import ( type StakingParams struct { // K-deep - ComfirmationTimeBlocks uint64 + ComfirmationTimeBlocks uint32 // W-deep - FinalizationTimeoutBlocks uint64 + FinalizationTimeoutBlocks uint32 // Minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx MinSlashingTxFeeSat btcutil.Amount @@ -52,9 +52,9 @@ type StakingParams struct { // MinimumUnbondingTime returns the minimum unbonding time. It is the bigger value from: // - MinUnbondingTime // - CheckpointFinalizationTimeout -func (p *StakingParams) MinimumUnbondingTime() uint64 { - return sdkmath.Max[uint64]( - uint64(p.MinUnbondingTime), +func (p *StakingParams) MinimumUnbondingTime() uint32 { + return sdkmath.Max[uint32]( + p.MinUnbondingTime, p.FinalizationTimeoutBlocks, ) }