diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76942b528..e6064f8cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,6 +138,28 @@ jobs: run: | make test-e2e-cache-btc-staking + e2e-run-btc-rewards: + needs: [e2e-docker-build-babylon] + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Download babylon artifact + uses: actions/download-artifact@v4 + with: + name: babylond-${{ github.sha }} + path: /tmp + - name: Docker load babylond + run: | + docker load < /tmp/docker-babylond.tar.gz + - name: Cache Go + uses: actions/setup-go@v5 + with: + go-version: 1.23 + - name: Run e2e TestBTCRewardsDistribution + run: | + make test-e2e-cache-btc-rewards + e2e-run-btc-staking-pre-approval: needs: [e2e-docker-build-babylon] runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9e5e7e2..92c46f8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Improvements +- [#306](https://github.com/babylonlabs-io/babylon/pull/306) feat: improve BTC reward distribution with +virtual block periods for each finality provider that has delegations and reward tracker structures. - [#338](https://github.com/babylonlabs-io/babylon/pull/338) Add print BIP-340 in `debug pubkey-raw` subcommand - [#316](https://github.com/babylonlabs-io/babylon/pull/316) Add testnet upgrade data @@ -95,6 +97,7 @@ to relay on UnbondingTime in delegation ## v0.17.2 ### Improvements + - [#311](https://github.com/babylonlabs-io/babylon/pull/311) Enforce version 2 for unbonding transactions diff --git a/Makefile b/Makefile index 58415f25f..5083406e3 100644 --- a/Makefile +++ b/Makefile @@ -280,6 +280,9 @@ test-e2e-cache-btc-timestamping-phase-2-rly: test-e2e-cache-btc-staking: go test -run TestBTCStakingTestSuite -mod=readonly -timeout=60m -v $(PACKAGES_E2E) --tags=e2e +test-e2e-cache-btc-rewards: + go test -run TestBTCRewardsDistribution -mod=readonly -timeout=60m -v $(PACKAGES_E2E) --tags=e2e + test-e2e-cache-btc-staking-pre-approval: go test -run TestBTCStakingPreApprovalTestSuite -mod=readonly -timeout=60m -v $(PACKAGES_E2E) --tags=e2e diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f11aec5b2..93333670d 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -167,8 +167,8 @@ message BTCDelegatorDelegationIndex { // BTCDelegationStatus is the status of a delegation. // There are two possible valid state transition paths for a BTC delegation: -// - PENDING -> ACTIVE -> UNBONDED -// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED +// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED -> EXPIRED +// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED/EXPIRED // and one invalid state transition path: // - PENDING -> VERIFIED -> UNBONDED i.e the staker unbonded before // activating delegation on Babylon chain. @@ -183,12 +183,14 @@ enum BTCDelegationStatus { VERIFIED = 1; // ACTIVE defines a delegation that has voting power ACTIVE = 2; - // UNBONDED defines a delegation no longer has voting power: - // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx with signatures from staker and covenant committee + // UNBONDED defines a delegation no longer has voting power + // by receiving unbonding tx with signatures from staker and covenant committee UNBONDED = 3; + // EXPIRED defines a delegation no longer has voting power + // for reaching the end of staking transaction timelock + EXPIRED = 4; // ANY is any of the above status - ANY = 4; + ANY = 5; } // SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index 21f67f837..90623e099 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -36,31 +36,16 @@ message FinalityProviderDistInfo { ]; // total_bonded_sat is the total amount of bonded BTC stake (in Satoshi) of the finality provider uint64 total_bonded_sat = 4; - // btc_dels is a list of BTC delegations' voting power information under this finality provider - repeated BTCDelDistInfo btc_dels = 5; // is_timestamped indicates whether the finality provider // has timestamped public randomness committed // if no, it should not be assigned voting power - bool is_timestamped = 6; + bool is_timestamped = 5; // is_jailed indicates whether the finality provider // is jailed, if so, it should not be assigned voting power - bool is_jailed = 7; + bool is_jailed = 6; // is_slashed indicates whether the finality provider // is slashed, if so, it should not be assigned voting power - bool is_slashed = 8; -} - -// BTCDelDistInfo contains the information related to voting power distribution for a BTC delegation -message BTCDelDistInfo { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ]; - // staker_addr is the address to receive rewards from BTC delegation. - string staker_addr = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // staking_tx_hash is the staking tx hash of the BTC delegation - string staking_tx_hash = 3; - // total_sat is the amount of BTC stake (in Satoshi) of the BTC delegation - uint64 total_sat = 4; + bool is_slashed = 7; } // IndexedBlock is the necessary metadata and finalization status of a block diff --git a/proto/babylon/incentive/rewards.proto b/proto/babylon/incentive/rewards.proto new file mode 100644 index 000000000..c322e15b1 --- /dev/null +++ b/proto/babylon/incentive/rewards.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; +package babylon.incentive; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/babylonlabs-io/babylon/x/incentive/types"; + +// FinalityProviderHistoricalRewards represents the cumulative rewards ratio of the +// finality provider per sat in that period. +// The period is ommited here and should be part of the key used to store this structure. +// Key: Prefix + Finality provider bech32 address + Period. +message FinalityProviderHistoricalRewards { + // The cumulative rewards of that finality provider per sat until that period + // This coins will aways increase the value, never be reduced due to keep acumulation + // and when the cumulative rewards will be used to distribute rewards, 2 periods will + // be loaded, calculate the difference and multiplied by the total sat amount delegated + // https://github.com/cosmos/cosmos-sdk/blob/e76102f885b71fd6e1c1efb692052173c4b3c3a3/x/distribution/keeper/delegation.go#L47 + repeated cosmos.base.v1beta1.Coin cumulative_rewards_per_sat = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // TODO(rafilx): add reference count for state prunning + // https://github.com/cosmos/cosmos-sdk/blob/d9c53bfefc1e75a3c6b09065ea8b3a836cda0d18/x/distribution/types/distribution.pb.go#L98 +} + +// FinalityProviderCurrentRewards represents the current rewards of the pool of +// BTC delegations that delegated for this finality provider is entitled to. +// Note: This rewards are for the BTC delegators that delegated to this FP +// the FP itself is not the owner or can withdraw this rewards. +// If a slash event happens with this finality provider, all the delegations need +// to withdraw to the RewardGauge and the related scrutures should be deleted. +// Key: Prefix + Finality provider bech32 address. +message FinalityProviderCurrentRewards { + // CurrentRewards is the current rewards that the finality provider have and it was not + // yet stored inside the FinalityProviderHistoricalRewards. Once something happens that + // modifies the amount of satoshis delegated to this finality provider or the delegators + // starting period (activation, unbonding or btc rewards withdraw) + // a new period must be created, accumulate this rewards to FinalityProviderHistoricalRewards + // with a new period and zero out the Current Rewards. + repeated cosmos.base.v1beta1.Coin current_rewards = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // Period stores the current period that serves as a reference for + // creating new historical rewards and correlate with BTCDelegationRewardsTracker + // StartPeriodCumulativeReward. + uint64 period = 2; + // TotalActiveSat is the total amount of active satoshi delegated + // to this finality provider. + bytes total_active_sat = 3 [ + (cosmos_proto.scalar) = "cosmos.Int", + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; +} + +// BTCDelegationRewardsTracker represents the structure that holds information +// from the last time this BTC delegator withdraw the rewards or modified his +// active staked amount to one finality provider. +// The finality provider address is ommitted here but should be part of the +// key used to store this structure together with the BTC delegator address. +message BTCDelegationRewardsTracker { + // StartPeriodCumulativeReward the starting period the the BTC delegator + // made his last withdraw of rewards or modified his active staking amount + // of satoshis. + uint64 start_period_cumulative_reward = 1; + // TotalActiveSat is the total amount of active satoshi delegated + // to one specific finality provider. + bytes total_active_sat = 2 [ + (cosmos_proto.scalar) = "cosmos.Int", + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; +} \ No newline at end of file diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto index 47a7c657d..42c543f6c 100644 --- a/proto/babylon/incentive/tx.proto +++ b/proto/babylon/incentive/tx.proto @@ -44,13 +44,13 @@ message MsgWithdrawRewardResponse { // MsgUpdateParams defines a message for updating incentive module parameters. message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; - + // authority is the address of the governance account. // just FYI: cosmos.AddressString marks that this field should use type alias // for AddressString instead of string, but the functionality is not yet implemented // in cosmos-proto string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - + // params defines the incentive parameters to update. // // NOTE: All parameters must be supplied. diff --git a/test/e2e/btc_rewards_distribution_e2e_test.go b/test/e2e/btc_rewards_distribution_e2e_test.go new file mode 100644 index 000000000..df4848a4e --- /dev/null +++ b/test/e2e/btc_rewards_distribution_e2e_test.go @@ -0,0 +1,680 @@ +package e2e + +import ( + "fmt" + "math" + "math/rand" + "strings" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/cometbft/cometbft/libs/bytes" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/babylonlabs-io/babylon/test/e2e/configurer" + "github.com/babylonlabs-io/babylon/test/e2e/configurer/chain" + "github.com/babylonlabs-io/babylon/testutil/coins" + "github.com/babylonlabs-io/babylon/testutil/datagen" + bbn "github.com/babylonlabs-io/babylon/types" + bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + ftypes "github.com/babylonlabs-io/babylon/x/finality/types" + itypes "github.com/babylonlabs-io/babylon/x/incentive/types" +) + +const ( + stakingTimeBlocks = uint16(math.MaxUint16) + wDel1 = "del1" + wDel2 = "del2" + wFp1 = "fp1" + wFp2 = "fp2" + numPubRand = uint64(350) +) + +type BtcRewardsDistribution struct { + suite.Suite + + r *rand.Rand + net *chaincfg.Params + + fp1BTCSK *btcec.PrivateKey + fp2BTCSK *btcec.PrivateKey + del1BTCSK *btcec.PrivateKey + del2BTCSK *btcec.PrivateKey + + fp1 *bstypes.FinalityProvider + fp2 *bstypes.FinalityProvider + + // 3 Delegations will start closely and possibly in the same block + // (fp1, del1), (fp1, del2), (fp2, del1) + + // (fp1, del1) fp1Del1StakingAmt => 2_00000000 + // (fp1, del2) fp1Del2StakingAmt => 4_00000000 + // (fp2, del1) fp2Del2StakingAmt => 2_00000000 + fp1Del1StakingAmt int64 + fp1Del2StakingAmt int64 + fp2Del1StakingAmt int64 + + // The lastet delegation will stake 6_00000000 to (fp2, del2). + // Since the rewards are combined by their bech32 address, del2 + // will have 10_00000000 and del1 will have 4_00000000 as voting power, + // meaning that del1 will receive only 40% of the amount of rewards + // that del2 will receive once every delegation is active and blocks + // are being rewarded. + fp2Del2StakingAmt int64 + + // bech32 address of the delegators + del1Addr string + del2Addr string + // bech32 address of the finality providers + fp1Addr string + fp2Addr string + + // covenant helpers + covenantSKs []*btcec.PrivateKey + covenantWallets []string + + // finality helpers + finalityIdx uint64 + finalityBlockHeightVoted uint64 + fp1RandListInfo *datagen.RandListInfo + fp2RandListInfo *datagen.RandListInfo + + configurer configurer.Configurer +} + +func (s *BtcRewardsDistribution) SetupSuite() { + s.T().Log("setting up e2e integration test suite...") + var err error + + s.r = rand.New(rand.NewSource(time.Now().Unix())) + s.net = &chaincfg.SimNetParams + s.fp1BTCSK, _, _ = datagen.GenRandomBTCKeyPair(s.r) + s.fp2BTCSK, _, _ = datagen.GenRandomBTCKeyPair(s.r) + s.del1BTCSK, _, _ = datagen.GenRandomBTCKeyPair(s.r) + s.del2BTCSK, _, _ = datagen.GenRandomBTCKeyPair(s.r) + + s.fp1Del1StakingAmt = int64(2 * 10e8) + s.fp1Del2StakingAmt = int64(4 * 10e8) + s.fp2Del1StakingAmt = int64(2 * 10e8) + s.fp2Del2StakingAmt = int64(6 * 10e8) + + covenantSKs, _, _ := bstypes.DefaultCovenantCommittee() + s.covenantSKs = covenantSKs + + s.configurer, err = configurer.NewBTCStakingConfigurer(s.T(), true) + s.NoError(err) + err = s.configurer.ConfigureChains() + s.NoError(err) + err = s.configurer.RunSetup() + s.NoError(err) +} + +// Test1CreateFinalityProviders creates all finality providers +func (s *BtcRewardsDistribution) Test1CreateFinalityProviders() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + + n1, err := chainA.GetNodeAtIndex(1) + s.NoError(err) + n2, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + s.fp1Addr = n1.KeysAdd(wFp1) + s.fp2Addr = n2.KeysAdd(wFp2) + + n2.BankMultiSendFromNode([]string{s.fp1Addr, s.fp2Addr}, "100000ubbn") + + n2.WaitForNextBlock() + + s.fp1 = CreateNodeFP( + s.T(), + s.r, + s.fp1BTCSK, + n1, + s.fp1Addr, + ) + s.NotNil(s.fp1) + + s.fp2 = CreateNodeFP( + s.T(), + s.r, + s.fp2BTCSK, + n2, + s.fp2Addr, + ) + s.NotNil(s.fp2) + + actualFps := n2.QueryFinalityProviders() + s.Len(actualFps, 2) +} + +// Test2CreateFinalityProviders creates the first 3 btc delegations +// with the same values, but different satoshi staked amounts +func (s *BtcRewardsDistribution) Test2CreateFirstBtcDelegations() { + n2, err := s.configurer.GetChainConfig(0).GetNodeAtIndex(2) + s.NoError(err) + + s.del1Addr = n2.KeysAdd(wDel1) + s.del2Addr = n2.KeysAdd(wDel2) + + n2.BankMultiSendFromNode([]string{s.del1Addr, s.del2Addr}, "100000ubbn") + + n2.WaitForNextBlock() + + // fp1Del1 + s.CreateBTCDelegationAndCheck(n2, wDel1, s.fp1, s.del1BTCSK, s.del1Addr, s.fp1Del1StakingAmt) + // fp1Del2 + s.CreateBTCDelegationAndCheck(n2, wDel2, s.fp1, s.del2BTCSK, s.del2Addr, s.fp1Del2StakingAmt) + // fp2Del1 + s.CreateBTCDelegationAndCheck(n2, wDel1, s.fp2, s.del1BTCSK, s.del1Addr, s.fp2Del1StakingAmt) +} + +// Test3SubmitCovenantSignature covenant approves all the 3 BTC delegation +func (s *BtcRewardsDistribution) Test3SubmitCovenantSignature() { + n1, err := s.configurer.GetChainConfig(0).GetNodeAtIndex(1) + s.NoError(err) + + params := n1.QueryBTCStakingParams() + + covAddrs := make([]string, params.CovenantQuorum) + covWallets := make([]string, params.CovenantQuorum) + for i := 0; i < int(params.CovenantQuorum); i++ { + covWallet := fmt.Sprintf("cov%d", i) + covWallets[i] = covWallet + covAddrs[i] = n1.KeysAdd(covWallet) + } + s.covenantWallets = covWallets + + n1.BankMultiSendFromNode(covAddrs, "100000ubbn") + + // tx bank send needs to take effect + n1.WaitForNextBlock() + + pendingDelsResp := n1.QueryFinalityProvidersDelegations(s.fp1.BtcPk.MarshalHex(), s.fp2.BtcPk.MarshalHex()) + s.Equal(len(pendingDelsResp), 3) + + for _, pendingDelResp := range pendingDelsResp { + pendingDel, err := ParseRespBTCDelToBTCDel(pendingDelResp) + s.NoError(err) + + SendCovenantSigsToPendingDel(s.r, s.T(), n1, s.net, s.covenantSKs, s.covenantWallets, pendingDel) + + n1.WaitForNextBlock() + } + + // wait for a block so that above txs take effect + n1.WaitForNextBlock() + + // ensure the BTC delegation has covenant sigs now + activeDelsSet := n1.QueryFinalityProvidersDelegations(s.fp1.BtcPk.MarshalHex(), s.fp2.BtcPk.MarshalHex()) + s.Len(activeDelsSet, 3) + for _, activeDel := range activeDelsSet { + s.True(activeDel.Active) + } +} + +// Test4CommitPublicRandomnessAndSealed commits public randomness for +// each finality provider and seals the epoch. +func (s *BtcRewardsDistribution) Test4CommitPublicRandomnessAndSealed() { + chainA := s.configurer.GetChainConfig(0) + n1, err := chainA.GetNodeAtIndex(1) + s.NoError(err) + n2, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // commit public randomness list + commitStartHeight := uint64(1) + + fp1RandListInfo, fp1CommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(s.r, s.fp1BTCSK, commitStartHeight, numPubRand) + s.NoError(err) + s.fp1RandListInfo = fp1RandListInfo + + fp2RandListInfo, fp2CommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(s.r, s.fp2BTCSK, commitStartHeight, numPubRand) + s.NoError(err) + s.fp2RandListInfo = fp2RandListInfo + + n1.CommitPubRandList( + fp1CommitPubRandList.FpBtcPk, + fp1CommitPubRandList.StartHeight, + fp1CommitPubRandList.NumPubRand, + fp1CommitPubRandList.Commitment, + fp1CommitPubRandList.Sig, + ) + + n2.CommitPubRandList( + fp2CommitPubRandList.FpBtcPk, + fp2CommitPubRandList.StartHeight, + fp2CommitPubRandList.NumPubRand, + fp2CommitPubRandList.Commitment, + fp2CommitPubRandList.Sig, + ) + + n1.WaitUntilCurrentEpochIsSealedAndFinalized(1) + + s.finalityBlockHeightVoted = n1.WaitFinalityIsActivated() + + // submit finality signature + s.finalityIdx = s.finalityBlockHeightVoted - commitStartHeight + + appHash := n1.AddFinalitySignatureToBlock( + s.fp1BTCSK, + s.fp1.BtcPk, + s.finalityBlockHeightVoted, + s.fp1RandListInfo.SRList[s.finalityIdx], + &s.fp1RandListInfo.PRList[s.finalityIdx], + *s.fp1RandListInfo.ProofList[s.finalityIdx].ToProto(), + fmt.Sprintf("--from=%s", wFp1), + ) + + n2.AddFinalitySignatureToBlock( + s.fp2BTCSK, + s.fp2.BtcPk, + s.finalityBlockHeightVoted, + s.fp2RandListInfo.SRList[s.finalityIdx], + &s.fp2RandListInfo.PRList[s.finalityIdx], + *s.fp2RandListInfo.ProofList[s.finalityIdx].ToProto(), + fmt.Sprintf("--from=%s", wFp2), + ) + + n2.WaitForNextBlock() + + // ensure vote is eventually cast + var finalizedBlocks []*ftypes.IndexedBlock + s.Eventually(func() bool { + finalizedBlocks = n1.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) + return len(finalizedBlocks) > 0 + }, time.Minute, time.Millisecond*50) + + s.Equal(s.finalityBlockHeightVoted, finalizedBlocks[0].Height) + s.Equal(appHash.Bytes(), finalizedBlocks[0].AppHash) + s.T().Logf("the block %d is finalized", s.finalityBlockHeightVoted) + s.AddFinalityVoteUntilCurrentHeight() +} + +// Test5CheckRewardsFirstDelegations verifies the rewards independent of mint amounts +func (s *BtcRewardsDistribution) Test5CheckRewardsFirstDelegations() { + n2, err := s.configurer.GetChainConfig(0).GetNodeAtIndex(2) + s.NoError(err) + + // Current setup of voting power + // (fp1, del1) => 2_00000000 + // (fp1, del2) => 4_00000000 + // (fp2, del1) => 2_00000000 + + // The sum per bech32 address will be + // (fp1) => 6_00000000 + // (fp2) => 2_00000000 + // (del1) => 4_00000000 + // (del2) => 4_00000000 + + // The rewards distributed for the finality providers should be fp1 => 3x, fp2 => 1x + fp1LastRewardGauge, fp2LastRewardGauge, btcDel1LastRewardGauge, btcDel2LastRewardGauge := s.QueryRewardGauges(n2) + + // fp1 ~2674ubbn + // fp2 ~891ubbn + coins.RequireCoinsDiffInPointOnePercentMargin( + s.T(), + fp2LastRewardGauge.Coins.MulInt(sdkmath.NewIntFromUint64(3)), // ~2673ubbn + fp1LastRewardGauge.Coins, + ) + + // The rewards distributed to the delegators should be the same for each delegator + // del1 ~7130ubbn + // del2 ~7130ubbn + coins.RequireCoinsDiffInPointOnePercentMargin(s.T(), btcDel1LastRewardGauge.Coins, btcDel2LastRewardGauge.Coins) + + CheckWithdrawReward(s.T(), n2, wDel2, s.del2Addr) + + s.AddFinalityVoteUntilCurrentHeight() +} + +// Test6ActiveLastDelegation creates a new btc delegation +// (fp2, del2) with 6_00000000 sats and sends the covenant signatures +// needed. +func (s *BtcRewardsDistribution) Test6ActiveLastDelegation() { + chainA := s.configurer.GetChainConfig(0) + n2, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + // covenants are at n1 + n1, err := chainA.GetNodeAtIndex(1) + s.NoError(err) + + // fp2Del2 + s.CreateBTCDelegationAndCheck(n2, wDel2, s.fp2, s.del2BTCSK, s.del2Addr, s.fp2Del2StakingAmt) + + s.AddFinalityVoteUntilCurrentHeight() + + allDelegations := n2.QueryFinalityProvidersDelegations(s.fp1.BtcPk.MarshalHex(), s.fp2.BtcPk.MarshalHex()) + s.Equal(len(allDelegations), 4) + + pendingDels := make([]*bstypes.BTCDelegationResponse, 0) + for _, delegation := range allDelegations { + if !strings.EqualFold(delegation.StatusDesc, bstypes.BTCDelegationStatus_PENDING.String()) { + continue + } + pendingDels = append(pendingDels, delegation) + } + + s.Equal(len(pendingDels), 1) + pendingDel, err := ParseRespBTCDelToBTCDel(pendingDels[0]) + s.NoError(err) + + SendCovenantSigsToPendingDel(s.r, s.T(), n1, s.net, s.covenantSKs, s.covenantWallets, pendingDel) + + // wait for a block so that covenant txs take effect + n1.WaitForNextBlock() + + s.AddFinalityVoteUntilCurrentHeight() + + // ensure that all BTC delegation are active + allDelegations = n1.QueryFinalityProvidersDelegations(s.fp1.BtcPk.MarshalHex(), s.fp2.BtcPk.MarshalHex()) + s.Len(allDelegations, 4) + for _, activeDel := range allDelegations { + s.True(activeDel.Active) + } +} + +// Test7CheckRewards verifies the rewards of all the delegations +// and finality provider +func (s *BtcRewardsDistribution) Test7CheckRewards() { + n2, err := s.configurer.GetChainConfig(0).GetNodeAtIndex(2) + s.NoError(err) + + n2.WaitForNextBlock() + s.AddFinalityVoteUntilCurrentHeight() + + // Current setup of voting power + // (fp1, del1) => 2_00000000 + // (fp1, del2) => 4_00000000 + // (fp2, del1) => 2_00000000 + // (fp2, del2) => 6_00000000 + + // The sum per bech32 address will be + // (fp1) => 6_00000000 + // (fp2) => 8_00000000 + // (del1) => 4_00000000 + // (del2) => 10_00000000 + fp1RewardGaugePrev, fp2RewardGaugePrev, btcDel1RewardGaugePrev, btcDel2RewardGaugePrev := s.QueryRewardGauges(n2) + // wait a few block of rewards to calculate the difference + n2.WaitForNextBlocks(2) + s.AddFinalityVoteUntilCurrentHeight() + n2.WaitForNextBlocks(2) + s.AddFinalityVoteUntilCurrentHeight() + n2.WaitForNextBlocks(2) + s.AddFinalityVoteUntilCurrentHeight() + n2.WaitForNextBlocks(2) + + fp1RewardGauge, fp2RewardGauge, btcDel1RewardGauge, btcDel2RewardGauge := s.QueryRewardGauges(n2) + + // since varius block were created, it is needed to get the difference + // from a certain point where all the delegations were active to properly + // calculate the distribution with the voting power structure with 4 BTC delegations active + // Note: if a new block is mined during the query of reward gauges, the calculation might be a + // bit off by some ubbn + fp1DiffRewards := fp1RewardGauge.Coins.Sub(fp1RewardGaugePrev.Coins...) + fp2DiffRewards := fp2RewardGauge.Coins.Sub(fp2RewardGaugePrev.Coins...) + del1DiffRewards := btcDel1RewardGauge.Coins.Sub(btcDel1RewardGaugePrev.Coins...) + del2DiffRewards := btcDel2RewardGauge.Coins.Sub(btcDel2RewardGaugePrev.Coins...) + + // Check the difference in the finality providers + // fp1 should receive ~75% of the rewards received by fp2 + expectedRwdFp1 := coins.CalculatePercentageOfCoins(fp2DiffRewards, 75) + coins.RequireCoinsDiffInPointOnePercentMargin(s.T(), fp1DiffRewards, expectedRwdFp1) + + // Check the difference in the delegators + // the del1 should receive ~40% of the rewards received by del2 + expectedRwdDel1 := coins.CalculatePercentageOfCoins(del2DiffRewards, 40) + coins.RequireCoinsDiffInPointOnePercentMargin(s.T(), del1DiffRewards, expectedRwdDel1) + + fp1DiffRewardsStr := fp1DiffRewards.String() + fp2DiffRewardsStr := fp2DiffRewards.String() + del1DiffRewardsStr := del1DiffRewards.String() + del2DiffRewardsStr := del2DiffRewards.String() + + s.NotEmpty(fp1DiffRewardsStr) + s.NotEmpty(fp2DiffRewardsStr) + s.NotEmpty(del1DiffRewardsStr) + s.NotEmpty(del2DiffRewardsStr) +} + +// TODO(rafilx): Slash a FP and expect rewards to be withdraw. + +func (s *BtcRewardsDistribution) AddFinalityVoteUntilCurrentHeight() { + chainA := s.configurer.GetChainConfig(0) + n1, err := chainA.GetNodeAtIndex(1) + s.NoError(err) + n2, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + currentBlock := n2.LatestBlockNumber() + + accN1, err := n1.QueryAccount(s.fp1.Addr) + s.NoError(err) + accN2, err := n1.QueryAccount(s.fp2.Addr) + s.NoError(err) + + accNumberN1 := accN1.GetAccountNumber() + accSequenceN1 := accN1.GetSequence() + + accNumberN2 := accN2.GetAccountNumber() + accSequenceN2 := accN2.GetSequence() + + for s.finalityBlockHeightVoted < currentBlock { + n1Flags := []string{ + "--offline", + fmt.Sprintf("--account-number=%d", accNumberN1), + fmt.Sprintf("--sequence=%d", accSequenceN1), + fmt.Sprintf("--from=%s", wFp1), + } + n2Flags := []string{ + "--offline", + fmt.Sprintf("--account-number=%d", accNumberN2), + fmt.Sprintf("--sequence=%d", accSequenceN2), + fmt.Sprintf("--from=%s", wFp2), + } + s.AddFinalityVote(n1Flags, n2Flags) + + accSequenceN1++ + accSequenceN2++ + } +} + +func (s *BtcRewardsDistribution) AddFinalityVote(flagsN1, flagsN2 []string) (appHash bytes.HexBytes) { + chainA := s.configurer.GetChainConfig(0) + n2, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + n1, err := chainA.GetNodeAtIndex(1) + s.NoError(err) + + s.finalityIdx++ + s.finalityBlockHeightVoted++ + + appHash = n1.AddFinalitySignatureToBlock( + s.fp1BTCSK, + s.fp1.BtcPk, + s.finalityBlockHeightVoted, + s.fp1RandListInfo.SRList[s.finalityIdx], + &s.fp1RandListInfo.PRList[s.finalityIdx], + *s.fp1RandListInfo.ProofList[s.finalityIdx].ToProto(), + flagsN1..., + ) + + n2.AddFinalitySignatureToBlock( + s.fp2BTCSK, + s.fp2.BtcPk, + s.finalityBlockHeightVoted, + s.fp2RandListInfo.SRList[s.finalityIdx], + &s.fp2RandListInfo.PRList[s.finalityIdx], + *s.fp2RandListInfo.ProofList[s.finalityIdx].ToProto(), + flagsN2..., + ) + + return appHash +} + +// QueryRewardGauges returns the rewards available for fp1, fp2, del1, del2 +func (s *BtcRewardsDistribution) QueryRewardGauges(n *chain.NodeConfig) ( + fp1, fp2, del1, del2 *itypes.RewardGaugesResponse, +) { + n.WaitForNextBlockWithSleep50ms() + + // tries to query all in the same block + fp1RewardGauges, errFp1 := n.QueryRewardGauge(s.fp1.Address()) + fp2RewardGauges, errFp2 := n.QueryRewardGauge(s.fp2.Address()) + btcDel1RewardGauges, errDel1 := n.QueryRewardGauge(sdk.MustAccAddressFromBech32(s.del1Addr)) + btcDel2RewardGauges, errDel2 := n.QueryRewardGauge(sdk.MustAccAddressFromBech32(s.del2Addr)) + s.NoError(errFp1) + s.NoError(errFp2) + s.NoError(errDel1) + s.NoError(errDel2) + + fp1RewardGauge, ok := fp1RewardGauges[itypes.FinalityProviderType.String()] + s.True(ok) + s.True(fp1RewardGauge.Coins.IsAllPositive()) + + fp2RewardGauge, ok := fp2RewardGauges[itypes.FinalityProviderType.String()] + s.True(ok) + s.True(fp2RewardGauge.Coins.IsAllPositive()) + + btcDel1RewardGauge, ok := btcDel1RewardGauges[itypes.BTCDelegationType.String()] + s.True(ok) + s.True(btcDel1RewardGauge.Coins.IsAllPositive()) + + btcDel2RewardGauge, ok := btcDel2RewardGauges[itypes.BTCDelegationType.String()] + s.True(ok) + s.True(btcDel2RewardGauge.Coins.IsAllPositive()) + + return fp1RewardGauge, fp2RewardGauge, btcDel1RewardGauge, btcDel2RewardGauge +} + +func (s *BtcRewardsDistribution) CreateBTCDelegationAndCheck( + n *chain.NodeConfig, + wDel string, + fp *bstypes.FinalityProvider, + btcStakerSK *btcec.PrivateKey, + delAddr string, + stakingSatAmt int64, +) { + n.CreateBTCDelegationAndCheck(s.r, s.T(), s.net, wDel, fp, btcStakerSK, delAddr, stakingTimeBlocks, stakingSatAmt) +} + +// CheckWithdrawReward withdraw rewards for one delegation and check the balance +func CheckWithdrawReward( + t testing.TB, + n *chain.NodeConfig, + delWallet, delAddr string, +) { + accDelAddr := sdk.MustAccAddressFromBech32(delAddr) + n.WaitForNextBlockWithSleep50ms() + + delBalanceBeforeWithdraw, err := n.QueryBalances(delAddr) + txHash := n.WithdrawReward(itypes.BTCDelegationType.String(), delWallet) + + n.WaitForNextBlock() + + _, txResp := n.QueryTx(txHash) + require.NoError(t, err) + + delRwdGauge, errRwdGauge := n.QueryRewardGauge(accDelAddr) + require.NoError(t, errRwdGauge) + + delBalanceAfterWithdraw, err := n.QueryBalances(delAddr) + require.NoError(t, err) + + // note that the rewards might not be precise as more or less blocks were produced and given out rewards + // while the query balance / withdraw / query gauge was running + delRewardGauge, ok := delRwdGauge[itypes.BTCDelegationType.String()] + require.True(t, ok) + require.True(t, delRewardGauge.Coins.IsAllPositive()) + + actualAmt := delBalanceAfterWithdraw.String() + expectedAmt := delBalanceBeforeWithdraw.Add(delRewardGauge.WithdrawnCoins...).Sub(txResp.AuthInfo.Fee.Amount...).String() + require.Equal(t, expectedAmt, actualAmt) +} + +func SendCovenantSigsToPendingDel( + r *rand.Rand, + t testing.TB, + n *chain.NodeConfig, + btcNet *chaincfg.Params, + covenantSKs []*btcec.PrivateKey, + covWallets []string, + pendingDel *bstypes.BTCDelegation, +) { + require.Len(t, pendingDel.CovenantSigs, 0) + + params := n.QueryBTCStakingParams() + slashingTx := pendingDel.SlashingTx + stakingTx := pendingDel.StakingTx + + stakingMsgTx, err := bbn.NewBTCTxFromBytes(stakingTx) + require.NoError(t, err) + stakingTxHash := stakingMsgTx.TxHash().String() + + fpBTCPKs, err := bbn.NewBTCPKsFromBIP340PKs(pendingDel.FpBtcPkList) + require.NoError(t, err) + + stakingInfo, err := pendingDel.GetStakingInfo(params, btcNet) + require.NoError(t, err) + + stakingSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + /* + generate and insert new covenant signature, in order to activate the BTC delegation + */ + // covenant signatures on slashing tx + covenantSlashingSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + fpBTCPKs, + stakingMsgTx, + stakingSlashingPathInfo.GetPkScriptPath(), + slashingTx, + ) + require.NoError(t, err) + + // cov Schnorr sigs on unbonding signature + unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() + require.NoError(t, err) + unbondingTx, err := bbn.NewBTCTxFromBytes(pendingDel.BtcUndelegation.UnbondingTx) + require.NoError(t, err) + + covUnbondingSigs, err := datagen.GenCovenantUnbondingSigs( + covenantSKs, + stakingMsgTx, + pendingDel.StakingOutputIdx, + unbondingPathInfo.GetPkScriptPath(), + unbondingTx, + ) + require.NoError(t, err) + + unbondingInfo, err := pendingDel.GetUnbondingInfo(params, btcNet) + require.NoError(t, err) + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + covenantUnbondingSlashingSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + fpBTCPKs, + unbondingTx, + unbondingSlashingPathInfo.GetPkScriptPath(), + pendingDel.BtcUndelegation.SlashingTx, + ) + require.NoError(t, err) + + for i := 0; i < int(params.CovenantQuorum); i++ { + // add covenant sigs + n.AddCovenantSigs( + covWallets[i], + covenantSlashingSigs[i].CovPk, + stakingTxHash, + covenantSlashingSigs[i].AdaptorSigs, + bbn.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), + covenantUnbondingSlashingSigs[i].AdaptorSigs, + ) + } +} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 29f47d602..9e30e42f1 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "math/rand" + "strings" "testing" "time" @@ -28,7 +29,6 @@ import ( "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" - ckpttypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" ftypes "github.com/babylonlabs-io/babylon/x/finality/types" itypes "github.com/babylonlabs-io/babylon/x/incentive/types" ) @@ -80,7 +80,7 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) - s.cacheFP = CreateNodeFP( + s.cacheFP = CreateNodeFPFromNodeAddr( s.T(), s.r, s.fptBTCSK, @@ -90,47 +90,23 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { /* create a random BTC delegation under this finality provider */ - // BTC staking params, BTC delegation key pairs and PoP - params := nonValidatorNode.QueryBTCStakingParams() - - // required unbonding time - unbondingTime := params.UnbondingTimeBlocks - - // NOTE: we use the node's address for the BTC delegation - stakerAddr := sdk.MustAccAddressFromBech32(nonValidatorNode.PublicAddress) - pop, err := bstypes.NewPoPBTC(stakerAddr, s.delBTCSK) - s.NoError(err) // generate staking tx and slashing tx stakingTimeBlocks := uint16(math.MaxUint16) - testStakingInfo, stakingTx, inclusionProof, testUnbondingInfo, delegatorSig := s.BTCStakingUnbondSlashInfo(nonValidatorNode, params, stakingTimeBlocks, s.cacheFP) - - delUnbondingSlashingSig, err := testUnbondingInfo.GenDelSlashingTxSig(s.delBTCSK) - s.NoError(err) - // submit the message for creating BTC delegation - nonValidatorNode.CreateBTCDelegation( - bbn.NewBIP340PubKeyFromBTCPK(s.delBTCSK.PubKey()), - pop, - stakingTx, - inclusionProof, - s.cacheFP.BtcPk, - stakingTimeBlocks, - btcutil.Amount(s.stakingValue), - testStakingInfo.SlashingTx, - delegatorSig, - testUnbondingInfo.UnbondingTx, - testUnbondingInfo.SlashingTx, - uint16(unbondingTime), - btcutil.Amount(testUnbondingInfo.UnbondingInfo.UnbondingOutput.Value), - delUnbondingSlashingSig, + // NOTE: we use the node's address for the BTC delegation + testStakingInfo := nonValidatorNode.CreateBTCDelegationAndCheck( + s.r, + s.T(), + s.net, nonValidatorNode.WalletName, - false, + s.cacheFP, + s.delBTCSK, + nonValidatorNode.PublicAddress, + stakingTimeBlocks, + s.stakingValue, ) - // wait for a block so that above txs take effect - nonValidatorNode.WaitForNextBlock() - pendingDelSet := nonValidatorNode.QueryFinalityProviderDelegations(s.cacheFP.BtcPk.MarshalHex()) s.Len(pendingDelSet, 1) pendingDels := pendingDelSet[0] @@ -222,9 +198,10 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { s.NoError(err) for i := 0; i < int(s.covenantQuorum); i++ { + // after adding the covenant signatures it panics with "BTC delegation rewards tracker has a negative amount of TotalActiveSat" nonValidatorNode.SubmitRefundableTxWithAssertion(func() { // add covenant sigs - nonValidatorNode.AddCovenantSigs( + nonValidatorNode.AddCovenantSigsFromVal( covenantSlashingSigs[i].CovPk, stakingTxHash, covenantSlashingSigs[i].AdaptorSigs, @@ -237,8 +214,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { } // wait for a block so that above txs take effect - nonValidatorNode.WaitForNextBlock() - nonValidatorNode.WaitForNextBlock() + nonValidatorNode.WaitForNextBlocks(2) // ensure the BTC delegation has covenant sigs now activeDelsSet := nonValidatorNode.QueryFinalityProviderDelegations(s.cacheFP.BtcPk.MarshalHex()) @@ -286,40 +262,11 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.ErrorContains(err, itypes.ErrRewardGaugeNotFound.Error()) delBabylonAddr := fpBabylonAddr - // finalize epochs from 1 to the current epoch - currentEpoch, err := nonValidatorNode.QueryCurrentEpoch() - s.NoError(err) - - // wait until the end epoch is sealed - s.Eventually(func() bool { - resp, err := nonValidatorNode.QueryRawCheckpoint(currentEpoch) - if err != nil { - return false - } - return resp.Status == ckpttypes.Sealed - }, time.Minute, time.Millisecond*50) - nonValidatorNode.FinalizeSealedEpochs(1, currentEpoch) - - // ensure the committed epoch is finalized - lastFinalizedEpoch := uint64(0) - s.Eventually(func() bool { - lastFinalizedEpoch, err = nonValidatorNode.QueryLastFinalizedEpoch() - if err != nil { - return false - } - return lastFinalizedEpoch >= currentEpoch - }, time.Minute, time.Millisecond*50) + nonValidatorNode.WaitUntilCurrentEpochIsSealedAndFinalized(1) // ensure btc staking is activated - var activatedHeight uint64 - s.Eventually(func() bool { - activatedHeight, err = nonValidatorNode.QueryActivatedHeight() - if err != nil { - return false - } - return activatedHeight > 0 - }, time.Minute, time.Millisecond*50) - s.T().Logf("the activated height is %d", activatedHeight) + // check how this does not errors out + activatedHeight := nonValidatorNode.WaitFinalityIsActivated() /* submit finality signature @@ -338,7 +285,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat nonValidatorNode.SubmitRefundableTxWithAssertion(func() { // submit finality signature - nonValidatorNode.AddFinalitySig(s.cacheFP.BtcPk, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) + nonValidatorNode.AddFinalitySigFromVal(s.cacheFP.BtcPk, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) // ensure vote is eventually cast var finalizedBlocks []*ftypes.IndexedBlock @@ -882,19 +829,62 @@ func equalFinalityProviderResp(t *testing.T, fp *bstypes.FinalityProvider, fpRes require.Equal(t, fp.SlashedBtcHeight, fpResp.SlashedBtcHeight) } +// CreateNodeFPFromNodeAddr creates a random finality provider. +func CreateNodeFPFromNodeAddr( + t *testing.T, + r *rand.Rand, + fpSk *btcec.PrivateKey, + node *chain.NodeConfig, +) (newFP *bstypes.FinalityProvider) { + // the node is the new FP + nodeAddr, err := sdk.AccAddressFromBech32(node.PublicAddress) + require.NoError(t, err) + + newFP, err = datagen.GenRandomFinalityProviderWithBTCBabylonSKs(r, fpSk, nodeAddr) + require.NoError(t, err) + + previousFps := node.QueryFinalityProviders() + + // use a higher commission to ensure the reward is more than tx fee of a finality sig + commission := sdkmath.LegacyNewDecWithPrec(20, 2) + newFP.Commission = &commission + node.CreateFinalityProvider(newFP.Addr, newFP.BtcPk, newFP.Pop, newFP.Description.Moniker, newFP.Description.Identity, newFP.Description.Website, newFP.Description.SecurityContact, newFP.Description.Details, newFP.Commission) + + // wait for a block so that above txs take effect + node.WaitForNextBlock() + + // query the existence of finality provider and assert equivalence + actualFps := node.QueryFinalityProviders() + require.Len(t, actualFps, len(previousFps)+1) + + for _, fpResp := range actualFps { + if !strings.EqualFold(fpResp.Addr, newFP.Addr) { + continue + } + equalFinalityProviderResp(t, newFP, fpResp) + return newFP + } + + return nil +} + // CreateNodeFP creates a random finality provider. func CreateNodeFP( t *testing.T, r *rand.Rand, fpSk *btcec.PrivateKey, - node *chain.NodeConfig) (newFP *bstypes.FinalityProvider) { + node *chain.NodeConfig, + fpAddr string, +) (newFP *bstypes.FinalityProvider) { // the node is the new FP - nodeAddr, err := sdk.AccAddressFromBech32(node.PublicAddress) + nodeAddr, err := sdk.AccAddressFromBech32(fpAddr) require.NoError(t, err) newFP, err = datagen.GenRandomFinalityProviderWithBTCBabylonSKs(r, fpSk, nodeAddr) require.NoError(t, err) + previousFps := node.QueryFinalityProviders() + // use a higher commission to ensure the reward is more than tx fee of a finality sig commission := sdkmath.LegacyNewDecWithPrec(20, 2) newFP.Commission = &commission @@ -905,11 +895,17 @@ func CreateNodeFP( // query the existence of finality provider and assert equivalence actualFps := node.QueryFinalityProviders() - require.Len(t, actualFps, 1) + require.Len(t, actualFps, len(previousFps)+1) - equalFinalityProviderResp(t, newFP, actualFps[0]) + for _, fpResp := range actualFps { + if !strings.EqualFold(fpResp.Addr, newFP.Addr) { + continue + } + equalFinalityProviderResp(t, newFP, fpResp) + return newFP + } - return newFP + return nil } // CovenantBTCPKs returns the covenantBTCPks as slice from parameters diff --git a/test/e2e/btc_staking_pre_approval_e2e_test.go b/test/e2e/btc_staking_pre_approval_e2e_test.go index 78d1b42c9..76fc7b7b1 100644 --- a/test/e2e/btc_staking_pre_approval_e2e_test.go +++ b/test/e2e/btc_staking_pre_approval_e2e_test.go @@ -78,7 +78,7 @@ func (s *BTCStakingPreApprovalTestSuite) Test1CreateFinalityProviderAndDelegatio nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) - s.cacheFP = CreateNodeFP( + s.cacheFP = CreateNodeFPFromNodeAddr( s.T(), s.r, s.fptBTCSK, @@ -221,7 +221,7 @@ func (s *BTCStakingPreApprovalTestSuite) Test2SubmitCovenantSignature() { for i := 0; i < int(s.covenantQuorum); i++ { nonValidatorNode.SubmitRefundableTxWithAssertion(func() { - nonValidatorNode.AddCovenantSigs( + nonValidatorNode.AddCovenantSigsFromVal( covenantSlashingSigs[i].CovPk, stakingTxHash, covenantSlashingSigs[i].AdaptorSigs, @@ -368,7 +368,7 @@ func (s *BTCStakingPreApprovalTestSuite) Test4CommitPublicRandomnessAndSubmitFin eotsSig := bbn.NewSchnorrEOTSSigFromModNScalar(sig) // submit finality signature nonValidatorNode.SubmitRefundableTxWithAssertion(func() { - nonValidatorNode.AddFinalitySig(s.cacheFP.BtcPk, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) + nonValidatorNode.AddFinalitySigFromVal(s.cacheFP.BtcPk, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) // ensure vote is eventually cast var finalizedBlocks []*ftypes.IndexedBlock @@ -386,7 +386,7 @@ func (s *BTCStakingPreApprovalTestSuite) Test4CommitPublicRandomnessAndSubmitFin _, pk, err := datagen.GenRandomBTCKeyPair(s.r) s.NoError(err) btcPK := bbn.NewBIP340PubKeyFromBTCPK(pk) - nonValidatorNode.AddFinalitySig(btcPK, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) + nonValidatorNode.AddFinalitySigFromVal(btcPK, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) nonValidatorNode.WaitForNextBlock() }, false) diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index 16c993a63..592501c35 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -288,13 +288,14 @@ func (n *NodeConfig) WasmExecute(contract, execMsg, from string) { } // WithdrawReward will withdraw the rewards of the address associated with the tx signer `from` -func (n *NodeConfig) WithdrawReward(sType, from string) { +func (n *NodeConfig) WithdrawReward(sType, from string) (txHash string) { n.LogActionF("withdraw rewards of type %s for tx signer %s", sType, from) cmd := []string{"babylond", "tx", "incentive", "withdraw-reward", sType, fmt.Sprintf("--from=%s", from)} n.LogActionF("Executing command: %s", strings.Join(cmd, " ")) - _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + outBuf, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - n.LogActionF("successfully withdrawn") + n.LogActionF("successfully withdrawn: %s", outBuf.String()) + return GetTxHashFromOutput(outBuf.String()) } // TxMultisigSign sign a tx in a file with one wallet for a multisig address. @@ -471,3 +472,18 @@ func (n *NodeConfig) SubmitRefundableTxWithAssertion( require.True(n.t, submitterBalanceBefore.IsAllGT(submitterBalanceAfter)) } } + +func GetTxHashFromOutput(txOutput string) (txHash string) { + // Define the regex pattern to match txhash + re := regexp.MustCompile(`txhash:\s*([A-Fa-f0-9]+)`) + + // Find the first match + match := re.FindStringSubmatch(txOutput) + + if len(match) > 1 { + // The first capture group contains the txhash value + txHash := match[1] + return txHash + } + return "" +} diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index d5f864861..07e378422 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -3,20 +3,30 @@ package chain import ( "encoding/hex" "fmt" + "math/rand" "strconv" "strings" + "testing" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/stretchr/testify/require" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" sdkmath "cosmossdk.io/math" + "github.com/cometbft/cometbft/libs/bytes" cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/babylonlabs-io/babylon/crypto/eots" asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonlabs-io/babylon/test/e2e/containers" + "github.com/babylonlabs-io/babylon/test/e2e/initialization" + "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" ) @@ -122,8 +132,19 @@ func (n *NodeConfig) CreateBTCDelegation( return outBuff.String() } -func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash string, slashingSigs [][]byte, unbondingSig *bbn.BIP340Signature, unbondingSlashingSigs [][]byte) { - n.LogActionF("adding covenant signature") +func (n *NodeConfig) AddCovenantSigsFromVal(covPK *bbn.BIP340PubKey, stakingTxHash string, slashingSigs [][]byte, unbondingSig *bbn.BIP340Signature, unbondingSlashingSigs [][]byte) { + n.AddCovenantSigs("val", covPK, stakingTxHash, slashingSigs, unbondingSig, unbondingSlashingSigs) +} + +func (n *NodeConfig) AddCovenantSigs( + fromWalletName string, + covPK *bbn.BIP340PubKey, + stakingTxHash string, + slashingSigs [][]byte, + unbondingSig *bbn.BIP340Signature, + unbondingSlashingSigs [][]byte, +) { + n.LogActionF("adding covenant signature from nodeName: %s", n.Name) covPKHex := covPK.MarshalHex() @@ -147,7 +168,7 @@ func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash stri cmd = append(cmd, unbondingSlashingSigStr) // used key - cmd = append(cmd, "--from=val") + cmd = append(cmd, fmt.Sprintf("--from=%s", fromWalletName)) // gas cmd = append(cmd, "--gas=auto", "--gas-adjustment=2") @@ -192,7 +213,15 @@ func (n *NodeConfig) CommitPubRandList(fpBTCPK *bbn.BIP340PubKey, startHeight ui n.LogActionF("successfully committed public randomness list") } -func (n *NodeConfig) AddFinalitySig(fpBTCPK *bbn.BIP340PubKey, blockHeight uint64, pubRand *bbn.SchnorrPubRand, proof cmtcrypto.Proof, appHash []byte, finalitySig *bbn.SchnorrEOTSSig) { +func (n *NodeConfig) AddFinalitySig( + fpBTCPK *bbn.BIP340PubKey, + blockHeight uint64, + pubRand *bbn.SchnorrPubRand, + proof cmtcrypto.Proof, + appHash []byte, + finalitySig *bbn.SchnorrEOTSSig, + overallFlags ...string, +) { n.LogActionF("add finality signature") fpBTCPKHex := fpBTCPK.MarshalHex() @@ -204,10 +233,25 @@ func (n *NodeConfig) AddFinalitySig(fpBTCPK *bbn.BIP340PubKey, blockHeight uint6 appHashHex := hex.EncodeToString(appHash) finalitySigHex := finalitySig.ToHexStr() - cmd := []string{"babylond", "tx", "finality", "add-finality-sig", fpBTCPKHex, blockHeightStr, pubRandHex, proofHex, appHashHex, finalitySigHex, "--from=val", "--gas=500000"} - _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + cmd := []string{"babylond", "tx", "finality", "add-finality-sig", fpBTCPKHex, blockHeightStr, pubRandHex, proofHex, appHashHex, finalitySigHex, "--gas=500000"} + additionalArgs := []string{fmt.Sprintf("--chain-id=%s", n.chainId), "--gas-prices=0.002ubbn", "-b=sync", "--yes", "--keyring-backend=test", "--log_format=json", "--home=/home/babylon/babylondata"} + cmd = append(cmd, additionalArgs...) + + outBuff, _, err := n.containerManager.ExecCmd(n.t, n.Name, append(cmd, overallFlags...), "code: 0") require.NoError(n.t, err) - n.LogActionF("successfully added finality signature") + n.LogActionF("successfully added finality signature: %s", outBuff.String()) +} + +func (n *NodeConfig) AddFinalitySigFromVal( + fpBTCPK *bbn.BIP340PubKey, + blockHeight uint64, + pubRand *bbn.SchnorrPubRand, + proof cmtcrypto.Proof, + appHash []byte, + finalitySig *bbn.SchnorrEOTSSig, + overallFlags ...string, +) { + n.AddFinalitySig(fpBTCPK, blockHeight, pubRand, proof, appHash, finalitySig, append(overallFlags, "--from=val")...) } func (n *NodeConfig) AddCovenantUnbondingSigs( @@ -262,3 +306,187 @@ func (n *NodeConfig) AddBTCDelegationInclusionProof( require.NoError(n.t, err) n.LogActionF("successfully added inclusion proof") } + +// BTCStakingUnbondSlashInfo generate BTC information to create BTC delegation. +func (n *NodeConfig) BTCStakingUnbondSlashInfo( + r *rand.Rand, + t testing.TB, + btcNet *chaincfg.Params, + params *bstypes.Params, + fp *bstypes.FinalityProvider, + btcStakerSK *btcec.PrivateKey, + stakingTimeBlocks uint16, + stakingSatAmt int64, +) ( + testStakingInfo *datagen.TestStakingSlashingInfo, + stakingTx []byte, + txInclusionProof *bstypes.InclusionProof, + testUnbondingInfo *datagen.TestUnbondingSlashingInfo, + delegatorSig *bbn.BIP340Signature, +) { + covenantBTCPKs := CovenantBTCPKs(params) + // required unbonding time + unbondingTime := params.UnbondingTimeBlocks + + testStakingInfo = datagen.GenBTCStakingSlashingInfo( + r, + t, + btcNet, + btcStakerSK, + []*btcec.PublicKey{fp.BtcPk.MustToBTCPK()}, + covenantBTCPKs, + params.CovenantQuorum, + stakingTimeBlocks, + stakingSatAmt, + params.SlashingPkScript, + params.SlashingRate, + uint16(unbondingTime), + ) + + // submit staking tx to Bitcoin and get inclusion proof + currentBtcTipResp, err := n.QueryTip() + require.NoError(t, err) + currentBtcTip, err := ParseBTCHeaderInfoResponseToInfo(currentBtcTipResp) + require.NoError(t, err) + + stakingMsgTx := testStakingInfo.StakingTx + + blockWithStakingTx := datagen.CreateBlockWithTransaction(r, currentBtcTip.Header.ToBlockHeader(), stakingMsgTx) + n.InsertHeader(&blockWithStakingTx.HeaderBytes) + // make block k-deep + for i := 0; i < initialization.BabylonBtcConfirmationPeriod; i++ { + n.InsertNewEmptyBtcHeader(r) + } + inclusionProof := bstypes.NewInclusionProofFromSpvProof(blockWithStakingTx.SpvProof) + + // generate BTC undelegation stuff + stkTxHash := testStakingInfo.StakingTx.TxHash() + unbondingValue := stakingSatAmt - datagen.UnbondingTxFee + testUnbondingInfo = datagen.GenBTCUnbondingSlashingInfo( + r, + t, + btcNet, + btcStakerSK, + []*btcec.PublicKey{fp.BtcPk.MustToBTCPK()}, + covenantBTCPKs, + params.CovenantQuorum, + wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), + stakingTimeBlocks, + unbondingValue, + params.SlashingPkScript, + params.SlashingRate, + uint16(unbondingTime), + ) + + stakingSlashingPathInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + delegatorSig, err = testStakingInfo.SlashingTx.Sign( + stakingMsgTx, + datagen.StakingOutIdx, + stakingSlashingPathInfo.GetPkScriptPath(), + btcStakerSK, + ) + require.NoError(t, err) + + return testStakingInfo, blockWithStakingTx.SpvProof.BtcTransaction, inclusionProof, testUnbondingInfo, delegatorSig +} + +func (n *NodeConfig) CreateBTCDelegationAndCheck( + r *rand.Rand, + t testing.TB, + btcNet *chaincfg.Params, + walletNameSender string, + fp *bstypes.FinalityProvider, + btcStakerSK *btcec.PrivateKey, + delAddr string, + stakingTimeBlocks uint16, + stakingSatAmt int64, +) (testStakingInfo *datagen.TestStakingSlashingInfo) { + // BTC staking params, BTC delegation key pairs and PoP + params := n.QueryBTCStakingParams() + + // NOTE: we use the node's address for the BTC delegation + del1Addr := sdk.MustAccAddressFromBech32(delAddr) + popDel1, err := bstypes.NewPoPBTC(del1Addr, btcStakerSK) + require.NoError(t, err) + + testStakingInfo, stakingTx, inclusionProof, testUnbondingInfo, delegatorSig := n.BTCStakingUnbondSlashInfo(r, t, btcNet, params, fp, btcStakerSK, stakingTimeBlocks, stakingSatAmt) + + delUnbondingSlashingSig, err := testUnbondingInfo.GenDelSlashingTxSig(btcStakerSK) + require.NoError(t, err) + + // submit the message for creating BTC delegation + n.CreateBTCDelegation( + bbn.NewBIP340PubKeyFromBTCPK(btcStakerSK.PubKey()), + popDel1, + stakingTx, + inclusionProof, + fp.BtcPk, + stakingTimeBlocks, + btcutil.Amount(stakingSatAmt), + testStakingInfo.SlashingTx, + delegatorSig, + testUnbondingInfo.UnbondingTx, + testUnbondingInfo.SlashingTx, + uint16(params.UnbondingTimeBlocks), + btcutil.Amount(testUnbondingInfo.UnbondingInfo.UnbondingOutput.Value), + delUnbondingSlashingSig, + walletNameSender, + false, + ) + + // wait for a block so that above txs take effect + n.WaitForNextBlock() + + // check if the address matches + btcDelegationResp := n.QueryBtcDelegation(testStakingInfo.StakingTx.TxHash().String()) + require.NotNil(t, btcDelegationResp) + require.Equal(t, btcDelegationResp.BtcDelegation.StakerAddr, delAddr) + require.Equal(t, btcStakerSK.PubKey().SerializeCompressed()[1:], btcDelegationResp.BtcDelegation.BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + + return testStakingInfo +} + +func (n *NodeConfig) AddFinalitySignatureToBlock( + fpBTCSK *secp256k1.PrivateKey, + fpBTCPK *bbn.BIP340PubKey, + blockHeight uint64, + privateRand *secp256k1.ModNScalar, + pubRand *bbn.SchnorrPubRand, + proof cmtcrypto.Proof, + overallFlags ...string, +) (blockVotedAppHash bytes.HexBytes) { + blockToVote, err := n.QueryBlock(int64(blockHeight)) + require.NoError(n.t, err) + appHash := blockToVote.AppHash + + msgToSign := append(sdk.Uint64ToBigEndian(blockHeight), appHash...) + // generate EOTS signature + fp1Sig, err := eots.Sign(fpBTCSK, privateRand, msgToSign) + require.NoError(n.t, err) + + finalitySig := bbn.NewSchnorrEOTSSigFromModNScalar(fp1Sig) + + // submit finality signature + n.AddFinalitySig( + fpBTCPK, + blockHeight, + pubRand, + proof, + appHash, + finalitySig, + overallFlags..., + ) + return appHash +} + +// CovenantBTCPKs returns the covenantBTCPks as slice from parameters +func CovenantBTCPKs(params *bstypes.Params) []*btcec.PublicKey { + // get covenant BTC PKs + covenantBTCPKs := make([]*btcec.PublicKey, len(params.CovenantPks)) + for i, covenantPK := range params.CovenantPks { + covenantBTCPKs[i] = covenantPK.MustToBTCPK() + } + return covenantBTCPKs +} diff --git a/test/e2e/configurer/chain/node.go b/test/e2e/configurer/chain/node.go index af2787e9c..9fc069616 100644 --- a/test/e2e/configurer/chain/node.go +++ b/test/e2e/configurer/chain/node.go @@ -120,9 +120,13 @@ func (n *NodeConfig) LatestBlockNumber() uint64 { } func (n *NodeConfig) WaitForCondition(doneCondition func() bool, errorMsg string) { + n.WaitForConditionWithPause(doneCondition, errorMsg, waitUntilRepeatPauseTime) +} + +func (n *NodeConfig) WaitForConditionWithPause(doneCondition func() bool, errorMsg string, pause time.Duration) { for i := 0; i < waitUntilrepeatMax; i++ { if !doneCondition() { - time.Sleep(waitUntilRepeatPauseTime) + time.Sleep(pause) continue } return @@ -154,6 +158,15 @@ func (n *NodeConfig) WaitForNextBlocks(numberOfBlocks uint64) { }, fmt.Sprintf("Timed out waiting for block %d. Current height is: %d", latest, blockToWait)) } +func (n *NodeConfig) WaitForNextBlockWithSleep50ms() { + latest := n.LatestBlockNumber() + blockToWait := latest + 1 + n.WaitForConditionWithPause(func() bool { + newLatest := n.LatestBlockNumber() + return newLatest > blockToWait + }, fmt.Sprintf("Timed out waiting for block %d. Current height is: %d", latest, blockToWait), time.Millisecond*50) +} + func (n *NodeConfig) extractOperatorAddressIfValidator() error { if !n.IsValidator { n.t.Logf("node (%s) is not a validator, skipping", n.Name) diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index 37ba3fbe6..c495e222c 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -18,6 +18,7 @@ import ( cmttypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" @@ -93,6 +94,25 @@ func (n *NodeConfig) QueryModuleAddress(name string) (sdk.AccAddress, error) { return account.GetAddress(), nil } +// QueryAccount returns the account given the address +func (n *NodeConfig) QueryAccount(address string) (sdk.AccountI, error) { + path := fmt.Sprintf("/cosmos/auth/v1beta1/accounts/%s", address) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp authtypes.QueryAccountResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return nil, err + } + + var account sdk.AccountI + if err := util.EncodingConfig.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { + return nil, err + } + + return account, nil +} + // QueryBalances returns balances at the address. func (n *NodeConfig) QueryBalances(address string) (sdk.Coins, error) { path := fmt.Sprintf("cosmos/bank/v1beta1/balances/%s", address) @@ -393,7 +413,7 @@ func (n *NodeConfig) QueryAppliedPlan(planName string) upgradetypes.QueryApplied return resp } -func (n *NodeConfig) QueryTx(txHash string, overallFlags ...string) sdk.TxResponse { +func (n *NodeConfig) QueryTx(txHash string, overallFlags ...string) (sdk.TxResponse, *sdktx.Tx) { cmd := []string{ "babylond", "q", "tx", "--type=hash", txHash, "--output=json", n.FlagChainID(), @@ -406,5 +426,45 @@ func (n *NodeConfig) QueryTx(txHash string, overallFlags ...string) sdk.TxRespon err = util.Cdc.UnmarshalJSON(out.Bytes(), &txResp) require.NoError(n.t, err) - return txResp + txAuth := txResp.Tx.GetCachedValue().(*sdktx.Tx) + return txResp, txAuth +} + +func (n *NodeConfig) WaitUntilCurrentEpochIsSealedAndFinalized(startEpoch uint64) (lastFinalizedEpoch uint64) { + // finalize epochs from 1 to the current epoch + currentEpoch, err := n.QueryCurrentEpoch() + require.NoError(n.t, err) + + // wait until the end epoch is sealed + require.Eventually(n.t, func() bool { + resp, err := n.QueryRawCheckpoint(currentEpoch) + if err != nil { + return false + } + return resp.Status == ct.Sealed + }, time.Minute*5, time.Millisecond*200) + n.FinalizeSealedEpochs(startEpoch, currentEpoch) + + // ensure the committed epoch is finalized + require.Eventually(n.t, func() bool { + lastFinalizedEpoch, err = n.QueryLastFinalizedEpoch() + if err != nil { + return false + } + return lastFinalizedEpoch >= currentEpoch + }, time.Minute, time.Millisecond*200) + return lastFinalizedEpoch +} + +func (n *NodeConfig) WaitFinalityIsActivated() (activatedHeight uint64) { + var err error + require.Eventually(n.t, func() bool { + activatedHeight, err = n.QueryActivatedHeight() + if err != nil { + return false + } + return activatedHeight > 0 + }, time.Minute*4, time.Second) + n.t.Logf("the activated height is %d", activatedHeight) + return activatedHeight } diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index cff41ecee..100136261 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -195,3 +195,14 @@ func (n *NodeConfig) QueryIndexedBlock(height uint64) *ftypes.IndexedBlock { return resp.Block } + +func (n *NodeConfig) QueryFinalityProvidersDelegations(fpsBTCPK ...string) []*bstypes.BTCDelegationResponse { + pendingDelsResp := make([]*bstypes.BTCDelegationResponse, 0) + for _, fpBTCPK := range fpsBTCPK { + fpDelsResp := n.QueryFinalityProviderDelegations(fpBTCPK) + for _, fpDel := range fpDelsResp { + pendingDelsResp = append(pendingDelsResp, fpDel.Dels...) + } + } + return pendingDelsResp +} diff --git a/test/e2e/containers/containers.go b/test/e2e/containers/containers.go index 22d6adc5e..b6863d1ef 100644 --- a/test/e2e/containers/containers.go +++ b/test/e2e/containers/containers.go @@ -61,7 +61,10 @@ func NewManager(identifier string, isDebugLogEnabled bool, isCosmosRelayer, isUp } // ExecTxCmd Runs ExecTxCmdWithSuccessString searching for `code: 0` -func (m *Manager) ExecTxCmd(t *testing.T, chainId string, nodeName string, command []string) (bytes.Buffer, bytes.Buffer, error) { +func (m *Manager) ExecTxCmd(t *testing.T, chainId string, nodeName string, command []string) ( + outBuf, errBuf bytes.Buffer, + err error, +) { return m.ExecTxCmdWithSuccessString(t, chainId, nodeName, command, "code: 0") } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 775151702..8af894aa0 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -20,10 +20,16 @@ func TestBTCTimestampingTestSuite(t *testing.T) { } // TestBTCStakingTestSuite tests BTC staking protocol end-to-end - func TestBTCStakingTestSuite(t *testing.T) { suite.Run(t, new(BTCStakingTestSuite)) } + +// TestBTCRewardsDistribution tests BTC staking rewards distribution end-to-end +// that involves x/btcstaking, x/finality, x/incentives and x/mint to give out rewards. +func TestBTCRewardsDistribution(t *testing.T) { + suite.Run(t, new(BtcRewardsDistribution)) +} + func TestBTCStakingPreApprovalTestSuite(t *testing.T) { suite.Run(t, new(BTCStakingPreApprovalTestSuite)) } diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index 1b5101a64..c1f4da6f7 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -358,6 +358,8 @@ func updateBtccheckpointGenesis(btccheckpointGenState *btccheckpointtypes.Genesi func updateFinalityGenesis(finalityGenState *finalitytypes.GenesisState) { finalityGenState.Params = finalitytypes.DefaultParams() finalityGenState.Params.FinalityActivationHeight = 0 + finalityGenState.Params.FinalitySigTimeout = 3 + finalityGenState.Params.SignedBlocksWindow = 300 } func updateGenUtilGenesis(c *internalChain) func(*genutiltypes.GenesisState) { diff --git a/test/e2e/software_upgrade_e2e_v1_test.go b/test/e2e/software_upgrade_e2e_v1_test.go index e28934a4d..cc1a4309e 100644 --- a/test/e2e/software_upgrade_e2e_v1_test.go +++ b/test/e2e/software_upgrade_e2e_v1_test.go @@ -111,7 +111,7 @@ func (s *SoftwareUpgradeV1TestnetTestSuite) TestUpgradeV1() { r := rand.New(rand.NewSource(time.Now().Unix())) fptBTCSK, _, _ := datagen.GenRandomBTCKeyPair(r) - fp := CreateNodeFP( + fp := CreateNodeFPFromNodeAddr( s.T(), r, fptBTCSK, diff --git a/testutil/btcstaking-helper/keeper.go b/testutil/btcstaking-helper/keeper.go index bcccb6023..ebb1a5bef 100644 --- a/testutil/btcstaking-helper/keeper.go +++ b/testutil/btcstaking-helper/keeper.go @@ -45,10 +45,10 @@ type Helper struct { FinalityKeeper *fkeeper.Keeper FMsgServer ftypes.MsgServer - BTCLightClientKeeper *types.MockBTCLightClientKeeper - BTCCheckpointKeeper *types.MockBtcCheckpointKeeper - CheckpointingKeeper *ftypes.MockCheckpointingKeeper - Net *chaincfg.Params + BTCLightClientKeeper *types.MockBTCLightClientKeeper + CheckpointingKeeperForBtcStaking *types.MockBtcCheckpointKeeper + CheckpointingKeeperForFinality *ftypes.MockCheckpointingKeeper + Net *chaincfg.Params } type UnbondingTxInfo struct { @@ -66,17 +66,32 @@ func NewHelper( // mock refundable messages iKeeper := ftypes.NewMockIncentiveKeeper(ctrl) iKeeper.EXPECT().IndexRefundableMsg(gomock.Any(), gomock.Any()).AnyTimes() + iKeeper.EXPECT().BtcDelegationActivated(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + iKeeper.EXPECT().BtcDelegationUnbonded(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + iKeeper.EXPECT().FpSlashed(gomock.Any(), gomock.Any()).AnyTimes() + + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) ckptKeeper := ftypes.NewMockCheckpointingKeeper(ctrl) ckptKeeper.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(timestampedEpoch).AnyTimes() - db := dbm.NewMemDB() - stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) + return NewHelperWithStoreAndIncentive(t, db, stateStore, btclcKeeper, btccKeeper, ckptKeeper, iKeeper) +} - k, _ := keepertest.BTCStakingKeeperWithStore(t, db, stateStore, btclcKeeper, btccKeeper, iKeeper) +func NewHelperWithStoreAndIncentive( + t testing.TB, + db dbm.DB, + stateStore store.CommitMultiStore, + btclcKeeper *types.MockBTCLightClientKeeper, + btccKForBtcStaking *types.MockBtcCheckpointKeeper, + btccKForFinality *ftypes.MockCheckpointingKeeper, + ictvKeeper ftypes.IncentiveKeeper, +) *Helper { + k, _ := keepertest.BTCStakingKeeperWithStore(t, db, stateStore, btclcKeeper, btccKForBtcStaking, ictvKeeper) msgSrvr := keeper.NewMsgServerImpl(*k) - fk, ctx := keepertest.FinalityKeeperWithStore(t, db, stateStore, k, iKeeper, ckptKeeper) + fk, ctx := keepertest.FinalityKeeperWithStore(t, db, stateStore, k, ictvKeeper, btccKForFinality) fMsgSrvr := fkeeper.NewMsgServerImpl(*fk) // set all parameters @@ -97,10 +112,10 @@ func NewHelper( FinalityKeeper: fk, FMsgServer: fMsgSrvr, - BTCLightClientKeeper: btclcKeeper, - BTCCheckpointKeeper: btccKeeper, - CheckpointingKeeper: ckptKeeper, - Net: &chaincfg.SimNetParams, + BTCLightClientKeeper: btclcKeeper, + CheckpointingKeeperForBtcStaking: btccKForBtcStaking, + CheckpointingKeeperForFinality: btccKForFinality, + Net: &chaincfg.SimNetParams, } } @@ -112,6 +127,10 @@ func (h *Helper) NoError(err error) { require.NoError(h.t, err) } +func (h *Helper) Equal(expected, actual interface{}) { + require.Equal(h.t, expected, actual) +} + func (h *Helper) Error(err error, msgAndArgs ...any) { require.Error(h.t, err, msgAndArgs...) } @@ -145,7 +164,7 @@ func (h *Helper) GenAndApplyCustomParams( params := btcctypes.DefaultParams() params.CheckpointFinalizationTimeout = finalizationTimeout - h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(params).AnyTimes() + h.CheckpointingKeeperForBtcStaking.EXPECT().GetParams(gomock.Any()).Return(params).AnyTimes() // randomise covenant committee covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) @@ -576,7 +595,7 @@ func (h *Helper) CommitPubRandList( epoch = timestampedEpoch + 1 } - h.CheckpointingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epoch}).Times(1) + h.CheckpointingKeeperForFinality.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epoch}).Times(1) _, err = h.FMsgServer.CommitPubRandList(h.Ctx, msg) h.NoError(err) diff --git a/testutil/coins/margin.go b/testutil/coins/margin.go new file mode 100644 index 000000000..0063b3aac --- /dev/null +++ b/testutil/coins/margin.go @@ -0,0 +1,69 @@ +package coins + +import ( + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func RequireCoinsDiffInPointOnePercentMargin(t *testing.T, c1, c2 sdk.Coins) { + inMargin := CoinsDiffInPointOnePercentMargin(c1, c2) + if !inMargin { + t.Log("Coins are not in 0.1% margin") + t.Logf("c1: %s", c1.String()) + t.Logf("c2: %s", c2.String()) + } + require.True(t, inMargin) +} + +func CoinsDiffInPointOnePercentMargin(c1, c2 sdk.Coins) bool { + diff, hasNeg := c1.SafeSub(c2...) + if hasNeg { + diff = AbsoluteCoins(diff) + } + margin := CalculatePointOnePercentOrMinOne(c1) + return margin.IsAllGTE(diff) +} + +func AbsoluteCoins(value sdk.Coins) sdk.Coins { + ret := sdk.NewCoins() + for _, v := range value { + ret = ret.Add(sdk.NewCoin(v.Denom, v.Amount.Abs())) + } + return ret +} + +// CalculatePointOnePercentOrMinOne transforms 10000 = 10 +// if 0.1% is of the value is less 1, sets one as value in the coin +func CalculatePointOnePercentOrMinOne(value sdk.Coins) sdk.Coins { + numerator := math.NewInt(1) + denominator := math.NewInt(1000) + result := value.MulInt(numerator).QuoInt(denominator) + return coinsAtLeastMinAmount(result, math.OneInt()) +} + +// CalculatePercentageOfCoins if percentage is 30, transforms 100bbn = 30bbn +func CalculatePercentageOfCoins(value sdk.Coins, percentage uint64) sdk.Coins { + divisor := math.NewInt(100) + multiplier := math.NewIntFromUint64(percentage) + result := value.MulInt(multiplier).QuoInt(divisor) + return result +} + +func coinsAtLeastMinAmount(value sdk.Coins, minAmt math.Int) sdk.Coins { + ret := sdk.NewCoins() + for _, v := range value { + minCoinAmt := coinAtLeastMinAmount(v, minAmt) + ret = ret.Add(minCoinAmt) + } + return ret +} + +func coinAtLeastMinAmount(v sdk.Coin, minAmt math.Int) sdk.Coin { + if v.Amount.GT(minAmt) { + return v + } + return sdk.NewCoin(v.Denom, minAmt) +} diff --git a/testutil/coins/margin_test.go b/testutil/coins/margin_test.go new file mode 100644 index 000000000..2819bbf26 --- /dev/null +++ b/testutil/coins/margin_test.go @@ -0,0 +1,40 @@ +package coins_test + +import ( + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/babylonlabs-io/babylon/testutil/coins" +) + +const ( + denom = "xsss" +) + +func TestCalculatePointOnePercent(t *testing.T) { + result := coins.CalculatePointOnePercentOrMinOne(sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(10000)))) + require.Equal(t, sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(10))).String(), result.String()) +} + +func TestCalculatePercentageOfCoins(t *testing.T) { + result := coins.CalculatePercentageOfCoins(sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(100))), 30) + require.Equal(t, sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(30))).String(), result.String()) + + result = coins.CalculatePercentageOfCoins(sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(1560))), 30) + require.Equal(t, sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(468))).String(), result.String()) +} + +func TestCoinsDiffInPointOnePercentMargin(t *testing.T) { + c1 := sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(10000))) + c2 := sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(9999))) + require.True(t, coins.CoinsDiffInPointOnePercentMargin(c1, c2)) + + limitC2 := sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(9990))) + require.True(t, coins.CoinsDiffInPointOnePercentMargin(c1, limitC2)) + + badC2 := sdk.NewCoins(sdk.NewCoin(denom, math.NewInt(9989))) + require.False(t, coins.CoinsDiffInPointOnePercentMargin(c1, badC2)) +} diff --git a/testutil/datagen/datagen.go b/testutil/datagen/datagen.go index 02fddaa01..f1f6e46c9 100644 --- a/testutil/datagen/datagen.go +++ b/testutil/datagen/datagen.go @@ -3,6 +3,8 @@ package datagen import ( "encoding/hex" "math/rand" + + "cosmossdk.io/math" ) func GenRandomByteArray(r *rand.Rand, length uint64) []byte { @@ -24,6 +26,10 @@ func RandomInt(r *rand.Rand, rng int) uint64 { return uint64(r.Intn(rng)) } +func RandomMathInt(r *rand.Rand, rng int) math.Int { + return math.NewIntFromUint64(RandomInt(r, rng)) +} + func RandomUInt32(r *rand.Rand, rng uint32) uint32 { return uint32(r.Intn(int(rng))) } diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index f22a5b82d..19c2da45e 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -3,6 +3,7 @@ package datagen import ( "math/rand" + "cosmossdk.io/math" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -42,7 +43,7 @@ func GenRandomCoins(r *rand.Rand) sdk.Coins { coins := sdk.NewCoins() for i := int32(0); i < numCoins; i++ { demon := GenRandomDenom(r) - amount := r.Int63n(10000) + 1 + amount := r.Int63n(10_000000) + 10_000000 coin := sdk.NewInt64Coin(demon, amount) coins = coins.Add(coin) } @@ -77,53 +78,55 @@ func GenRandomGauge(r *rand.Rand) *itypes.Gauge { return itypes.NewGauge(coins...) } -func GenRandomBTCDelDistInfo(r *rand.Rand) (*ftypes.BTCDelDistInfo, error) { - btcPK, err := GenRandomBIP340PubKey(r) - if err != nil { - return nil, err - } - return &ftypes.BTCDelDistInfo{ - BtcPk: btcPK, - StakerAddr: GenRandomAccount().Address, - TotalSat: RandomInt(r, 1000) + 1, - }, nil +func GenRandomAddrAndSat(r *rand.Rand) (string, uint64) { + return GenRandomAccount().Address, RandomInt(r, 1000) + 1 } -func GenRandomFinalityProviderDistInfo(r *rand.Rand) (*ftypes.FinalityProviderDistInfo, error) { +func GenRandomFinalityProviderDistInfo(r *rand.Rand) ( + fpDistInfo *ftypes.FinalityProviderDistInfo, + btcTotalSatByAddress map[string]uint64, + err error, +) { // create finality provider with random commission fp, err := GenRandomFinalityProvider(r) if err != nil { - return nil, err + return nil, nil, err } // create finality provider distribution info - fpDistInfo := ftypes.NewFinalityProviderDistInfo(fp) + fpDistInfo = ftypes.NewFinalityProviderDistInfo(fp) // add a random number of BTC delegation distribution info numBTCDels := RandomInt(r, 100) + 1 + btcTotalSatByAddress = make(map[string]uint64, numBTCDels) for i := uint64(0); i < numBTCDels; i++ { - btcDelDistInfo, err := GenRandomBTCDelDistInfo(r) - if err != nil { - return nil, err - } - fpDistInfo.BtcDels = append(fpDistInfo.BtcDels, btcDelDistInfo) - fpDistInfo.TotalBondedSat += btcDelDistInfo.TotalSat + btcAddr, totalSat := GenRandomAddrAndSat(r) + btcTotalSatByAddress[btcAddr] += totalSat + fpDistInfo.TotalBondedSat += totalSat fpDistInfo.IsTimestamped = true } - return fpDistInfo, nil + return fpDistInfo, btcTotalSatByAddress, nil } -func GenRandomVotingPowerDistCache(r *rand.Rand, maxFPs uint32) (*ftypes.VotingPowerDistCache, error) { - dc := ftypes.NewVotingPowerDistCache() +func GenRandomVotingPowerDistCache(r *rand.Rand, maxFPs uint32) ( + dc *ftypes.VotingPowerDistCache, + // fpAddr => delAddr => totalSat + btcTotalSatByDelAddressByFpAddress map[string]map[string]uint64, + err error, +) { + dc = ftypes.NewVotingPowerDistCache() // a random number of finality providers numFps := RandomInt(r, 10) + 1 + + btcTotalSatByDelAddressByFpAddress = make(map[string]map[string]uint64, numFps) for i := uint64(0); i < numFps; i++ { - v, err := GenRandomFinalityProviderDistInfo(r) + v, btcTotalSatByAddress, err := GenRandomFinalityProviderDistInfo(r) if err != nil { - return nil, err + return nil, nil, err } + btcTotalSatByDelAddressByFpAddress[v.GetAddress().String()] = btcTotalSatByAddress dc.AddFinalityProviderDistInfo(v) } dc.ApplyActiveFinalityProviders(maxFPs) - return dc, nil + return dc, btcTotalSatByDelAddressByFpAddress, nil } func GenRandomCheckpointAddressPair(r *rand.Rand) *btcctypes.CheckpointAddressPair { @@ -142,3 +145,34 @@ func GenRandomBTCTimestampingRewardDistInfo(r *rand.Rand) *btcctypes.RewardDistI } return btcctypes.NewRewardDistInfo(best, others...) } + +func GenRandomFinalityProviderCurrentRewards(r *rand.Rand) itypes.FinalityProviderCurrentRewards { + rwd := GenRandomCoins(r) + period := RandomInt(r, 100) + 3 + activeSatoshi := RandomMathInt(r, 10000).AddRaw(10) + return itypes.NewFinalityProviderCurrentRewards(rwd, period, activeSatoshi) +} + +func GenRandomBTCDelegationRewardsTracker(r *rand.Rand) itypes.BTCDelegationRewardsTracker { + period := RandomInt(r, 100) + 2 + activeSatoshi := RandomMathInt(r, 10000).Add(math.NewInt(100)) + return itypes.NewBTCDelegationRewardsTracker(period, activeSatoshi) +} + +func GenRandomFPHistRwd(r *rand.Rand) itypes.FinalityProviderHistoricalRewards { + rwd := GenRandomCoins(r) + return itypes.NewFinalityProviderHistoricalRewards(rwd) +} + +func GenRandomFPHistRwdWithDecimals(r *rand.Rand) itypes.FinalityProviderHistoricalRewards { + rwd := GenRandomFPHistRwd(r) + rwd.CumulativeRewardsPerSat = rwd.CumulativeRewardsPerSat.MulInt(itypes.DecimalAccumulatedRewards) + return rwd +} + +func GenRandomFPHistRwdStartAndEnd(r *rand.Rand) (start, end itypes.FinalityProviderHistoricalRewards) { + start = GenRandomFPHistRwdWithDecimals(r) + end = GenRandomFPHistRwdWithDecimals(r) + end.CumulativeRewardsPerSat = end.CumulativeRewardsPerSat.Add(start.CumulativeRewardsPerSat...) + return start, end +} diff --git a/testutil/incentives-helper/keeper.go b/testutil/incentives-helper/keeper.go new file mode 100644 index 000000000..9f2012e77 --- /dev/null +++ b/testutil/incentives-helper/keeper.go @@ -0,0 +1,193 @@ +package testutil + +import ( + "math/rand" + "testing" + + "cosmossdk.io/log" + "cosmossdk.io/store" + storemetrics "cosmossdk.io/store/metrics" + "github.com/btcsuite/btcd/btcec/v2" + dbm "github.com/cosmos/cosmos-db" + sdk "github.com/cosmos/cosmos-sdk/types" + bankk "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/golang/mock/gomock" + + btcstkhelper "github.com/babylonlabs-io/babylon/testutil/btcstaking-helper" + testutil "github.com/babylonlabs-io/babylon/testutil/btcstaking-helper" + "github.com/babylonlabs-io/babylon/testutil/datagen" + keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" + btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" + "github.com/babylonlabs-io/babylon/x/btcstaking/types" + bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + ftypes "github.com/babylonlabs-io/babylon/x/finality/types" + "github.com/babylonlabs-io/babylon/x/incentive/keeper" +) + +type IncentiveHelper struct { + *btcstkhelper.Helper + BankKeeper bankk.Keeper + IncentivesKeeper *keeper.Keeper +} + +func NewIncentiveHelper( + t testing.TB, + btclcKeeper *bstypes.MockBTCLightClientKeeper, + btccKForBtcStaking *bstypes.MockBtcCheckpointKeeper, + btccKForFinality *ftypes.MockCheckpointingKeeper, +) *IncentiveHelper { + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) + + accK := keepertest.AccountKeeper(t, db, stateStore) + bankK := keepertest.BankKeeper(t, db, stateStore, accK) + + ictvK, _ := keepertest.IncentiveKeeperWithStore(t, db, stateStore, bankK, accK, nil) + btcstkH := btcstkhelper.NewHelperWithStoreAndIncentive(t, db, stateStore, btclcKeeper, btccKForBtcStaking, btccKForFinality, ictvK) + + return &IncentiveHelper{ + Helper: btcstkH, + BankKeeper: bankK, + IncentivesKeeper: ictvK, + } +} + +func (h *IncentiveHelper) SetFinalityActivationHeight(newActivationHeight uint64) { + finalityParams := h.FinalityKeeper.GetParams(h.Ctx) + finalityParams.FinalityActivationHeight = newActivationHeight + err := h.FinalityKeeper.SetParams(h.Ctx, finalityParams) + h.NoError(err) +} + +func (h *IncentiveHelper) CreateBtcDelegation( + r *rand.Rand, + fpPK *secp256k1.PublicKey, + stakingValue int64, + stakingTime uint16, + btcLightClientTipHeight uint32, +) ( + stakingTxHash string, msgCreateBTCDel *bstypes.MsgCreateBTCDelegation, actualDel *bstypes.BTCDelegation, unbondingInfo *btcstkhelper.UnbondingTxInfo, +) { + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + h.NoError(err) + + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + h.NoError(err) + + stakingTxHash, msgCreateBTCDel, _, _, _, unbondingInfo, err = h.CreateDelegationWithBtcBlockHeight( + r, + delSK, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + stakingTime, + 0, + 0, + false, + false, + 10, + btcLightClientTipHeight, + ) + h.NoError(err) + + // ensure consistency between the msg and the BTC delegation in DB + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + + h.Equal(msgCreateBTCDel.StakerAddr, actualDel.StakerAddr) + h.Equal(msgCreateBTCDel.Pop, actualDel.Pop) + h.Equal(msgCreateBTCDel.StakingTx, actualDel.StakingTx) + h.Equal(msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) + + return stakingTxHash, msgCreateBTCDel, actualDel, unbondingInfo +} + +func (h *IncentiveHelper) CreateActiveBtcDelegation( + r *rand.Rand, + covenantSKs []*secp256k1.PrivateKey, + fpPK *secp256k1.PublicKey, + stakingValue int64, + stakingTime uint16, + btcLightClientTipHeight uint32, +) ( + stakingTxHash string, actualDel *bstypes.BTCDelegation, unbondingInfo *btcstkhelper.UnbondingTxInfo, +) { + stakingTxHash, msgCreateBTCDel, actualDel, unbondingInfo := h.CreateBtcDelegation(r, fpPK, stakingValue, stakingTime, btcLightClientTipHeight) + + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: btcLightClientTipHeight}).MaxTimes(len(bsParams.CovenantPks) * 2) + + h.GenerateAndSendCovenantSignatures(r, covenantSKs, msgCreateBTCDel, actualDel) + h.EqualBtcDelegationStatus(stakingTxHash, btcLightClientTipHeight, bstypes.BTCDelegationStatus_ACTIVE) + return stakingTxHash, actualDel, unbondingInfo +} + +func (h *IncentiveHelper) EqualBtcDelRwdTrackerActiveSat(fp, del sdk.AccAddress, expectedSatAmount uint64) { + btcDel, err := h.IncentivesKeeper.GetBTCDelegationRewardsTracker(h.Ctx, fp, del) + h.NoError(err) + h.Equal(btcDel.TotalActiveSat.Uint64(), expectedSatAmount) +} + +func (h *IncentiveHelper) BtcUndelegate( + stakingTxHash string, + del *bstypes.BTCDelegation, + unbondingInfo *testutil.UnbondingTxInfo, + btcLightClientTipHeight uint32, +) { + msgUndelegate := &bstypes.MsgBTCUndelegate{ + Signer: datagen.GenRandomAccount().Address, + StakingTxHash: stakingTxHash, + StakeSpendingTx: del.BtcUndelegation.UnbondingTx, + StakeSpendingTxInclusionProof: unbondingInfo.UnbondingTxInclusionProof, + } + + // early unbond + _, err := h.MsgServer.BTCUndelegate(h.Ctx, msgUndelegate) + h.NoError(err) + + h.EqualBtcDelegationStatus(stakingTxHash, btcLightClientTipHeight, bstypes.BTCDelegationStatus_UNBONDED) +} + +func (h *IncentiveHelper) GenerateAndSendCovenantSignatures( + r *rand.Rand, + covenantSKs []*btcec.PrivateKey, + msgCreateBTCDel *types.MsgCreateBTCDelegation, + del *types.BTCDelegation, +) { + covMsgs := h.GenerateCovenantSignaturesMessages(r, covenantSKs, msgCreateBTCDel, del) + for _, msg := range covMsgs { + _, err := h.MsgServer.AddCovenantSigs(h.Ctx, msg) + h.NoError(err) + } +} + +func (h *IncentiveHelper) EqualBtcDelegationStatus( + stakingTxHashStr string, + tipHeight uint32, + expectedStatus bstypes.BTCDelegationStatus, +) { + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHashStr) + h.NoError(err) + + covenantQuorum := h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum + + status := actualDel.GetStatus(tipHeight, covenantQuorum) + h.Equal(expectedStatus, status) +} + +func (h *IncentiveHelper) CtxAddBlkHeight(blocksToAdd int64) { + headerInfo := h.Ctx.HeaderInfo() + headerInfo.Height += blocksToAdd + h.Ctx = h.Ctx.WithHeaderInfo(headerInfo) +} + +func (h *IncentiveHelper) FpAddPubRand(r *rand.Rand, sk *btcec.PrivateKey, startHeight uint64) *datagen.RandListInfo { + numPubRand := uint64(200) + randListInfo, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, sk, startHeight, numPubRand) + h.NoError(err) + + _, err = h.FMsgServer.CommitPubRandList(h.Ctx, msgCommitPubRandList) + h.NoError(err) + return randListInfo +} diff --git a/testutil/keeper/account.go b/testutil/keeper/account.go new file mode 100644 index 000000000..f4d7a8e9a --- /dev/null +++ b/testutil/keeper/account.go @@ -0,0 +1,45 @@ +package keeper + +import ( + "testing" + + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + accountk "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/require" + + "github.com/babylonlabs-io/babylon/app" + appparams "github.com/babylonlabs-io/babylon/app/params" +) + +func AccountKeeper( + t testing.TB, + db dbm.DB, + stateStore store.CommitMultiStore, +) accountk.AccountKeeper { + storeKey := storetypes.NewKVStoreKey(authtypes.StoreKey) + + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + k := accountk.NewAccountKeeper( + cdc, + runtime.NewKVStoreService(storeKey), + authtypes.ProtoBaseAccount, + app.GetMaccPerms(), + authcodec.NewBech32Codec(appparams.Bech32PrefixAccAddr), + appparams.Bech32PrefixAccAddr, + appparams.AccGov.String(), + ) + + return k +} diff --git a/testutil/keeper/bank.go b/testutil/keeper/bank.go new file mode 100644 index 000000000..11f9749c3 --- /dev/null +++ b/testutil/keeper/bank.go @@ -0,0 +1,44 @@ +package keeper + +import ( + "testing" + + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + bankk "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + + appparams "github.com/babylonlabs-io/babylon/app/params" +) + +func BankKeeper( + t testing.TB, + db dbm.DB, + stateStore store.CommitMultiStore, + accountKeeper banktypes.AccountKeeper, +) bankk.Keeper { + storeKey := storetypes.NewKVStoreKey(banktypes.StoreKey) + + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + k := bankk.NewBaseKeeper( + cdc, + runtime.NewKVStoreService(storeKey), + accountKeeper, + map[string]bool{}, + appparams.AccGov.String(), + log.NewNopLogger(), + ) + + return k +} diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go index bd274cdfb..4e2128b5b 100644 --- a/testutil/keeper/incentive.go +++ b/testutil/keeper/incentive.go @@ -22,11 +22,16 @@ import ( "github.com/babylonlabs-io/babylon/x/incentive/types" ) -func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, epochingKeeper types.EpochingKeeper) (*keeper.Keeper, sdk.Context) { +func IncentiveKeeperWithStore( + t testing.TB, + db dbm.DB, + stateStore store.CommitMultiStore, + bankKeeper types.BankKeeper, + accountKeeper types.AccountKeeper, + epochingKeeper types.EpochingKeeper, +) (*keeper.Keeper, sdk.Context) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := dbm.NewMemDB() - stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) require.NoError(t, stateStore.LoadLatestVersion()) @@ -46,10 +51,19 @@ func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper ty ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) ctx = ctx.WithHeaderInfo(header.Info{}) + return &k, ctx +} + +func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, epochingKeeper types.EpochingKeeper) (*keeper.Keeper, sdk.Context) { + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) + + k, ctx := IncentiveKeeperWithStore(t, db, stateStore, bankKeeper, accountKeeper, epochingKeeper) + // Initialize params if err := k.SetParams(ctx, types.DefaultParams()); err != nil { panic(err) } - return &k, ctx + return k, ctx } diff --git a/testutil/store/state.go b/testutil/store/state.go new file mode 100644 index 000000000..da203a1b3 --- /dev/null +++ b/testutil/store/state.go @@ -0,0 +1,36 @@ +package store + +import ( + "testing" + + "cosmossdk.io/core/header" + corestore "cosmossdk.io/core/store" + "cosmossdk.io/log" + "cosmossdk.io/store" + storemetrics "cosmossdk.io/store/metrics" + storetypes "cosmossdk.io/store/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func NewStoreService(t *testing.T, moduleName string) (kvStore corestore.KVStoreService, stateStore storetypes.CommitMultiStore) { + db := dbm.NewMemDB() + stateStore = store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) + + storeKey := storetypes.NewKVStoreKey(moduleName) + + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + require.NoError(t, stateStore.LoadLatestVersion()) + + return runtime.NewKVStoreService(storeKey), stateStore +} + +func NewStoreWithCtx(t *testing.T, moduleName string) (ctx sdk.Context, kvStore corestore.KVStoreService) { + kvStore, stateStore := NewStoreService(t, moduleName) + ctx = sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) + return ctx, kvStore +} diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index 0c2357ff3..5cfb64b2e 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -70,16 +70,16 @@ func (k Keeper) AddBTCDelegation( panic(fmt.Errorf("failed to emit EventBTCDelegationInclusionProofReceived for the new pending BTC delegation: %w", err)) } - // record event that the BTC delegation will become unbonded at EndHeight-w + // record event that the BTC delegation will become expired (unbonded) at EndHeight-w // This event will be generated to subscribers as block event, when the // btc light client block height will reach btcDel.EndHeight-wValue - unbondedEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ + expiredEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ StakingTxHash: stakingTxHash.String(), - NewState: types.BTCDelegationStatus_UNBONDED, + NewState: types.BTCDelegationStatus_EXPIRED, }) // NOTE: we should have verified that EndHeight > btcTip.Height + unbonding_time - k.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-btcDel.UnbondingTime, unbondedEvent) + k.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-btcDel.UnbondingTime, expiredEvent) } return nil diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index 550a899fa..1c42113fa 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" + "github.com/btcsuite/btcd/chaincfg/chainhash" bbn "github.com/babylonlabs-io/babylon/types" diff --git a/x/btcstaking/keeper/genesis_test.go b/x/btcstaking/keeper/genesis_test.go index 9bec4f07f..5f7b65938 100644 --- a/x/btcstaking/keeper/genesis_test.go +++ b/x/btcstaking/keeper/genesis_test.go @@ -103,10 +103,10 @@ func TestExportGenesis(t *testing.T) { DelBtcPk: del.BtcPk, } - // record event that the BTC delegation will become unbonded at EndHeight-w + // record event that the BTC delegation will become expired (unbonded) at EndHeight-w unbondedEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ StakingTxHash: stakingTxHash.String(), - NewState: types.BTCDelegationStatus_UNBONDED, + NewState: types.BTCDelegationStatus_EXPIRED, }) // events diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 3a08512a6..0ef12a442 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -110,8 +110,6 @@ func (ms msgServer) EditFinalityProvider(goCtx context.Context, req *types.MsgEd return nil, types.ErrCommissionGTMaxRate } - // TODO: check to index the finality provider by his address instead of the BTC pk - // find the finality provider with the given BTC PK fp, err := ms.GetFinalityProvider(goCtx, req.BtcPk) if err != nil { return nil, err @@ -394,13 +392,13 @@ func (ms msgServer) AddBTCDelegationInclusionProof( ms.addPowerDistUpdateEvent(ctx, timeInfo.TipHeight, activeEvent) // record event that the BTC delegation will become unbonded at EndHeight-w - unbondedEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ + expiredEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ StakingTxHash: req.StakingTxHash, - NewState: types.BTCDelegationStatus_UNBONDED, + NewState: types.BTCDelegationStatus_EXPIRED, }) // NOTE: we should have verified that EndHeight > btcTip.Height + min_unbonding_time - ms.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-params.UnbondingTimeBlocks, unbondedEvent) + ms.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-params.UnbondingTimeBlocks, expiredEvent) // at this point, the BTC delegation inclusion proof is verified and is not duplicated // thus, we can safely consider this message as refundable @@ -457,7 +455,7 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove // ensure BTC delegation is still pending, i.e., not unbonded btcTipHeight := ms.btclcKeeper.GetTipInfo(ctx).Height status := btcDel.GetStatus(btcTipHeight, params.CovenantQuorum) - if status == types.BTCDelegationStatus_UNBONDED { + if status == types.BTCDelegationStatus_UNBONDED || status == types.BTCDelegationStatus_EXPIRED { ms.Logger(ctx).Debug("Received covenant signature after the BTC delegation is already unbonded", "covenant pk", req.Pk.MarshalHex()) return nil, types.ErrInvalidCovenantSig.Wrap("the BTC delegation is already unbonded") } @@ -608,7 +606,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele bsParams.CovenantQuorum, ) - if btcDelStatus == types.BTCDelegationStatus_UNBONDED { + if btcDelStatus == types.BTCDelegationStatus_UNBONDED || btcDelStatus == types.BTCDelegationStatus_EXPIRED { return nil, types.ErrInvalidBTCUndelegateReq.Wrap("cannot unbond an unbonded BTC delegation") } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index f32bde022..2ce6051a6 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -749,6 +749,76 @@ func FuzzBTCUndelegate(f *testing.F) { }) } +func FuzzBTCUndelegateExpired(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := testutil.NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new finality provider + _, fpPK, _ := h.CreateFinalityProvider(r) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + h.NoError(err) + stakingTxHash, msgCreateBTCDel, actualDel, btcHeaderInfo, inclusionProof, unbondingInfo, err := h.CreateDelegationWithBtcBlockHeight( + r, + delSK, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + 0, + 0, + true, + false, + 10, + 10, + ) + h.NoError(err) + + // add covenant signatures to this BTC delegation + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel, 10) + // activate the BTC delegation + btcTip := uint32(30) + h.AddInclusionProof(stakingTxHash, btcHeaderInfo, inclusionProof, btcTip) + + // ensure the BTC delegation is bonded right now + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + status := actualDel.GetStatus(btcTip, bsParams.CovenantQuorum) + require.Equal(t, types.BTCDelegationStatus_ACTIVE, status) + + msg := &types.MsgBTCUndelegate{ + Signer: datagen.GenRandomAccount().Address, + StakingTxHash: stakingTxHash, + StakeSpendingTx: actualDel.BtcUndelegation.UnbondingTx, + StakeSpendingTxInclusionProof: unbondingInfo.UnbondingTxInclusionProof, + } + + // expires the delegation + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(&btclctypes.BTCHeaderInfo{Height: 2000}).AnyTimes() + _, err = h.MsgServer.BTCUndelegate(h.Ctx, msg) + require.EqualError(t, err, types.ErrInvalidBTCUndelegateReq.Wrap("cannot unbond an unbonded BTC delegation").Error()) + }) +} + func FuzzSelectiveSlashing(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index aba0e8c87..4a71cee90 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -29,6 +29,8 @@ func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, er return BTCDelegationStatus_ACTIVE, nil case "unbonded": return BTCDelegationStatus_UNBONDED, nil + case "expired": + return BTCDelegationStatus_EXPIRED, nil case "any": return BTCDelegationStatus_ANY, nil default: @@ -59,6 +61,11 @@ func (d *BTCDelegation) GetFpIdx(fpBTCPK *bbn.BIP340PubKey) int { return -1 } +// Address returns the bech32 BTC staker address +func (d *BTCDelegation) Address() sdk.AccAddress { + return sdk.MustAccAddressFromBech32(d.StakerAddr) +} + func (d *BTCDelegation) GetCovSlashingAdaptorSig( covBTCPK *bbn.BIP340PubKey, valIdx int, @@ -127,12 +134,18 @@ func (d *BTCDelegation) GetStatus( // At this point we already have covenant quorum and inclusion proof, // we can check the status based on the BTC height - if btcHeight < d.StartHeight || btcHeight+d.UnbondingTime > d.EndHeight { + if btcHeight < d.StartHeight { // staking tx's timelock has not begun, or is less than unbonding time BTC - // blocks left, or is expired + // blocks left return BTCDelegationStatus_UNBONDED } + // if the endheight is lower than the btc height + unbonding time + // the btc delegation should be considered expired + if btcHeight+d.UnbondingTime > d.EndHeight { + return BTCDelegationStatus_EXPIRED + } + // - we have covenant quorum // - we have inclusion proof // - we are not unbonded early @@ -347,7 +360,6 @@ func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Para return unbondingInfo, nil } -// TODO: verify to remove, not used in babylon, only for tests // findFPIdx returns the index of the given finality provider // among all restaked finality providers func (d *BTCDelegation) findFPIdx(fpBTCPK *bbn.BIP340PubKey) (int, error) { @@ -363,7 +375,6 @@ func (d *BTCDelegation) findFPIdx(fpBTCPK *bbn.BIP340PubKey) (int, error) { // the signatures on the slashing tx, such that the slashing tx obtains full // witness and can be submitted to Bitcoin. // This happens after the finality provider is slashed and its SK is extracted. -// TODO: verify not used func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, fpSK *btcec.PrivateKey) (*wire.MsgTx, error) { stakingMsgTx, err := bbn.NewBTCTxFromBytes(d.StakingTx) if err != nil { @@ -414,7 +425,6 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha return slashingMsgTxWithWitness, nil } -// TODO: verify to remove, func not used by babylon, used in side car processes. func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, fpSK *btcec.PrivateKey) (*wire.MsgTx, error) { unbondingMsgTx, err := bbn.NewBTCTxFromBytes(d.BtcUndelegation.UnbondingTx) if err != nil { diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index b7cdfa49d..8675de6e5 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -17,6 +17,11 @@ func (fp *FinalityProvider) IsJailed() bool { return fp.Jailed } +// Address returns the bech32 fp address +func (fp *FinalityProvider) Address() sdk.AccAddress { + return sdk.MustAccAddressFromBech32(fp.Addr) +} + func (fp *FinalityProvider) ValidateBasic() error { // ensure fields are non-empty and well-formatted if _, err := sdk.AccAddressFromBech32(fp.Addr); err != nil { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 03eb03051..e79ca336b 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -30,8 +30,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // BTCDelegationStatus is the status of a delegation. // There are two possible valid state transition paths for a BTC delegation: -// - PENDING -> ACTIVE -> UNBONDED -// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED +// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED -> EXPIRED +// - PENDING -> VERIFIED -> ACTIVE -> UNBONDED/EXPIRED // and one invalid state transition path: // - PENDING -> VERIFIED -> UNBONDED i.e the staker unbonded before // activating delegation on Babylon chain. @@ -48,12 +48,14 @@ const ( BTCDelegationStatus_VERIFIED BTCDelegationStatus = 1 // ACTIVE defines a delegation that has voting power BTCDelegationStatus_ACTIVE BTCDelegationStatus = 2 - // UNBONDED defines a delegation no longer has voting power: - // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx with signatures from staker and covenant committee + // UNBONDED defines a delegation no longer has voting power + // by receiving unbonding tx with signatures from staker and covenant committee BTCDelegationStatus_UNBONDED BTCDelegationStatus = 3 + // EXPIRED defines a delegation no longer has voting power + // for reaching the end of staking transaction timelock + BTCDelegationStatus_EXPIRED BTCDelegationStatus = 4 // ANY is any of the above status - BTCDelegationStatus_ANY BTCDelegationStatus = 4 + BTCDelegationStatus_ANY BTCDelegationStatus = 5 ) var BTCDelegationStatus_name = map[int32]string{ @@ -61,7 +63,8 @@ var BTCDelegationStatus_name = map[int32]string{ 1: "VERIFIED", 2: "ACTIVE", 3: "UNBONDED", - 4: "ANY", + 4: "EXPIRED", + 5: "ANY", } var BTCDelegationStatus_value = map[string]int32{ @@ -69,7 +72,8 @@ var BTCDelegationStatus_value = map[string]int32{ "VERIFIED": 1, "ACTIVE": 2, "UNBONDED": 3, - "ANY": 4, + "EXPIRED": 4, + "ANY": 5, } func (x BTCDelegationStatus) String() string { @@ -927,94 +931,95 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1388 bytes of a gzipped FileDescriptorProto + // 1398 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0xdb, 0x46, - 0x16, 0x36, 0x25, 0xf9, 0xef, 0x48, 0xb2, 0x95, 0x89, 0xd6, 0xcb, 0xc4, 0x58, 0xdb, 0xab, 0xcd, - 0x06, 0xc2, 0x6e, 0x2c, 0xc5, 0x4e, 0x80, 0xcd, 0xb6, 0x68, 0x01, 0xcb, 0x72, 0x1a, 0xa1, 0x89, - 0xad, 0x52, 0xb2, 0x8b, 0x16, 0x28, 0x58, 0x8a, 0x1c, 0x53, 0x53, 0x49, 0x1c, 0x96, 0x33, 0x52, - 0xe5, 0xa7, 0x68, 0x0b, 0xf4, 0x21, 0xfa, 0x00, 0xb9, 0xec, 0x03, 0xe4, 0x32, 0x08, 0x7a, 0x51, - 0xf8, 0xc2, 0x28, 0x92, 0x17, 0x29, 0x66, 0x38, 0x22, 0x69, 0xd7, 0x4e, 0x9d, 0xd8, 0x77, 0x9c, - 0xf3, 0xf7, 0x1d, 0x7e, 0xe7, 0x9b, 0x19, 0x12, 0xee, 0x76, 0xac, 0xce, 0x51, 0x9f, 0x7a, 0xd5, - 0x0e, 0xb7, 0x19, 0xb7, 0x7a, 0xc4, 0x73, 0xab, 0xa3, 0x8d, 0xc4, 0xaa, 0xe2, 0x07, 0x94, 0x53, - 0xf4, 0x37, 0x15, 0x57, 0x49, 0x78, 0x46, 0x1b, 0xb7, 0x8b, 0x2e, 0x75, 0xa9, 0x8c, 0xa8, 0x8a, - 0xa7, 0x30, 0xf8, 0xf6, 0x2d, 0x9b, 0xb2, 0x01, 0x65, 0x66, 0xe8, 0x08, 0x17, 0xca, 0x75, 0x27, - 0x5c, 0x55, 0x63, 0xac, 0x0e, 0xe6, 0xd6, 0x46, 0xf5, 0x14, 0xda, 0xed, 0xd5, 0xf3, 0xbb, 0xf2, - 0xa9, 0xaf, 0x02, 0xee, 0x25, 0x02, 0xec, 0x2e, 0xb6, 0x7b, 0x3e, 0x25, 0x1e, 0x57, 0x9d, 0xc7, - 0x86, 0x30, 0xba, 0xf4, 0x53, 0x06, 0x0a, 0x8f, 0x89, 0x67, 0xf5, 0x09, 0x3f, 0x6a, 0x06, 0x74, - 0x44, 0x1c, 0x1c, 0xa0, 0x7b, 0x90, 0xb1, 0x1c, 0x27, 0xd0, 0xb5, 0x35, 0xad, 0x3c, 0x5f, 0xd3, - 0x5f, 0x3d, 0x5f, 0x2f, 0xaa, 0x4e, 0xb7, 0x1c, 0x27, 0xc0, 0x8c, 0xb5, 0x78, 0x40, 0x3c, 0xd7, - 0x90, 0x51, 0x68, 0x07, 0xb2, 0x0e, 0x66, 0x76, 0x40, 0x7c, 0x4e, 0xa8, 0xa7, 0xa7, 0xd6, 0xb4, - 0x72, 0x76, 0xf3, 0x5f, 0x15, 0x95, 0x11, 0x33, 0x22, 0xdf, 0xa6, 0x52, 0x8f, 0x43, 0x8d, 0x64, - 0x1e, 0x7a, 0x06, 0x60, 0xd3, 0xc1, 0x80, 0x30, 0x26, 0xaa, 0xa4, 0x25, 0xf4, 0xfa, 0xf1, 0xc9, - 0xea, 0x72, 0x58, 0x88, 0x39, 0xbd, 0x0a, 0xa1, 0xd5, 0x81, 0xc5, 0xbb, 0x95, 0xa7, 0xd8, 0xb5, - 0xec, 0xa3, 0x3a, 0xb6, 0x5f, 0x3d, 0x5f, 0x07, 0x85, 0x53, 0xc7, 0xb6, 0x91, 0x28, 0x80, 0xf6, - 0x60, 0xa6, 0xc3, 0x6d, 0xd3, 0xef, 0xe9, 0x99, 0x35, 0xad, 0x9c, 0xab, 0x3d, 0x3a, 0x3e, 0x59, - 0x7d, 0xe8, 0x12, 0xde, 0x1d, 0x76, 0x2a, 0x36, 0x1d, 0x54, 0x15, 0x4b, 0x7d, 0xab, 0xc3, 0xd6, - 0x09, 0x9d, 0x2c, 0xab, 0xfc, 0xc8, 0xc7, 0xac, 0x52, 0x6b, 0x34, 0x1f, 0x3c, 0xbc, 0xdf, 0x1c, - 0x76, 0x3e, 0xc5, 0x47, 0xc6, 0x74, 0x87, 0xdb, 0xcd, 0x1e, 0xfa, 0x08, 0xd2, 0x3e, 0xf5, 0xf5, - 0x69, 0xf9, 0x7a, 0xff, 0xad, 0x9c, 0x3b, 0xf4, 0x4a, 0x33, 0xa0, 0xf4, 0x70, 0xef, 0xb0, 0x49, - 0x19, 0xc3, 0xb2, 0x8f, 0x5a, 0x7b, 0xdb, 0x10, 0x79, 0xe8, 0x21, 0x2c, 0xb1, 0xbe, 0xc5, 0xba, - 0xd8, 0x31, 0x55, 0xaa, 0xd9, 0xc5, 0xc4, 0xed, 0x72, 0x7d, 0x66, 0x4d, 0x2b, 0x67, 0x8c, 0xa2, - 0xf2, 0xd6, 0x42, 0xe7, 0x13, 0xe9, 0x43, 0xf7, 0x00, 0x45, 0x59, 0xdc, 0x9e, 0x64, 0xcc, 0xae, - 0x69, 0xe5, 0xbc, 0x51, 0x98, 0x64, 0x70, 0x5b, 0x45, 0x2f, 0xc1, 0xcc, 0x37, 0x16, 0xe9, 0x63, - 0x47, 0x9f, 0x5b, 0xd3, 0xca, 0x73, 0x86, 0x5a, 0xa1, 0xfb, 0x50, 0xec, 0x12, 0xb7, 0x8b, 0x19, - 0x37, 0x47, 0x94, 0x63, 0x67, 0x52, 0x67, 0x5e, 0xd6, 0x41, 0xca, 0x77, 0x20, 0x5c, 0x61, 0xa5, - 0xd2, 0xaf, 0x29, 0xd0, 0xcf, 0xca, 0xe2, 0x73, 0xc2, 0xbb, 0xcf, 0x30, 0xb7, 0x12, 0xd4, 0x6a, - 0xd7, 0x43, 0xed, 0x12, 0xcc, 0xa8, 0x8e, 0x52, 0x92, 0x0b, 0xb5, 0x42, 0xff, 0x84, 0xdc, 0x88, - 0x72, 0xe2, 0xb9, 0xa6, 0x4f, 0xbf, 0xc3, 0x81, 0x14, 0x45, 0xc6, 0xc8, 0x86, 0xb6, 0xa6, 0x30, - 0xbd, 0x85, 0xd6, 0xcc, 0x3b, 0xd3, 0x3a, 0xfd, 0x97, 0xb4, 0xce, 0x5c, 0x8a, 0xd6, 0xd9, 0x0b, - 0x69, 0xfd, 0x65, 0x16, 0xf2, 0xb5, 0xf6, 0x76, 0x1d, 0xf7, 0xb1, 0x6b, 0x49, 0xd5, 0xff, 0x1f, - 0xb2, 0x42, 0x3e, 0x38, 0x30, 0x2f, 0xb5, 0xe3, 0x20, 0x0c, 0x16, 0xc6, 0xc4, 0x18, 0x52, 0xd7, - 0xaa, 0xf0, 0xf4, 0x7b, 0x2a, 0xfc, 0x2b, 0x58, 0x38, 0xf4, 0xcd, 0xb0, 0x25, 0xb3, 0x4f, 0x98, - 0x18, 0x41, 0xfa, 0x4a, 0x7d, 0x65, 0x0f, 0xfd, 0x9a, 0xe8, 0xec, 0x29, 0x61, 0x52, 0x0c, 0xaa, - 0x0d, 0x93, 0x93, 0x01, 0x56, 0xd3, 0xca, 0x2a, 0x5b, 0x9b, 0x0c, 0xb0, 0x0a, 0x09, 0x78, 0x72, - 0x67, 0x85, 0x21, 0x01, 0x57, 0xb3, 0xfc, 0x07, 0x00, 0xf6, 0xce, 0x4c, 0x6a, 0x1e, 0x7b, 0x6a, - 0x40, 0x68, 0x19, 0xe6, 0x39, 0xe5, 0x56, 0xdf, 0x64, 0x16, 0x97, 0x9b, 0x28, 0x63, 0xcc, 0x49, - 0x43, 0xcb, 0x92, 0xb9, 0x51, 0x07, 0x63, 0xb9, 0x79, 0x72, 0xc6, 0xfc, 0x04, 0x7f, 0x2c, 0x45, - 0xa5, 0xdc, 0x74, 0xc8, 0xfd, 0x21, 0x37, 0x89, 0x33, 0xd6, 0x41, 0x89, 0x2a, 0xf4, 0xec, 0x49, - 0x47, 0xc3, 0x19, 0xa3, 0x4d, 0xc8, 0x4a, 0xa1, 0xa9, 0x6a, 0x59, 0x39, 0xc2, 0x1b, 0xc7, 0x27, - 0xab, 0x42, 0x20, 0x2d, 0xe5, 0x69, 0x8f, 0x0d, 0x60, 0xd1, 0x33, 0xfa, 0x1a, 0xf2, 0x4e, 0x28, - 0x1d, 0x1a, 0x98, 0x8c, 0xb8, 0x7a, 0x4e, 0x66, 0x7d, 0x78, 0x7c, 0xb2, 0xfa, 0xbf, 0x77, 0x23, - 0xb8, 0x45, 0x5c, 0xcf, 0xe2, 0xc3, 0x00, 0x1b, 0xb9, 0xa8, 0x62, 0x8b, 0xb8, 0x68, 0x1f, 0xf2, - 0x36, 0x1d, 0x61, 0xcf, 0xf2, 0xb8, 0x00, 0x60, 0x7a, 0x7e, 0x2d, 0x5d, 0xce, 0x6e, 0xde, 0xbf, - 0x40, 0x0c, 0xdb, 0x2a, 0x76, 0xcb, 0xb1, 0xfc, 0xb0, 0x42, 0x58, 0x95, 0x19, 0xb9, 0x49, 0x99, - 0x16, 0x71, 0x19, 0xfa, 0x37, 0x2c, 0x0c, 0xbd, 0x0e, 0xf5, 0x9c, 0x68, 0x7a, 0x0b, 0x92, 0x96, - 0x7c, 0x64, 0x95, 0xf3, 0xfb, 0x0c, 0x0a, 0x42, 0x3e, 0x43, 0xcf, 0x89, 0x36, 0x88, 0xbe, 0x28, - 0xd5, 0x78, 0xf7, 0x82, 0x06, 0x6a, 0xed, 0xed, 0xfd, 0x44, 0xb4, 0xb1, 0xd8, 0xe1, 0x76, 0xd2, - 0x20, 0x90, 0x7d, 0x2b, 0xb0, 0x06, 0xcc, 0x1c, 0xe1, 0x40, 0xde, 0x2c, 0x85, 0x10, 0x39, 0xb4, - 0x1e, 0x84, 0x46, 0x74, 0x07, 0x16, 0x04, 0x32, 0x27, 0xfe, 0x44, 0x1a, 0x37, 0x64, 0x58, 0xae, - 0xc3, 0xed, 0x36, 0xf1, 0xd5, 0xf6, 0xfd, 0x18, 0x96, 0xea, 0x13, 0xb6, 0xf6, 0x27, 0x9d, 0x37, - 0xbc, 0x43, 0x2a, 0xf2, 0x99, 0x2f, 0x84, 0x25, 0xf7, 0xa7, 0x18, 0xa8, 0x3c, 0x1a, 0x8d, 0x9c, - 0xb4, 0xb6, 0x84, 0xb1, 0x3d, 0x2e, 0xfd, 0x98, 0x81, 0xc5, 0x33, 0x1d, 0x0b, 0xcd, 0x26, 0xa8, - 0x99, 0xe4, 0x65, 0x63, 0x62, 0xfe, 0x24, 0x95, 0xd4, 0x65, 0xa4, 0xf2, 0x2d, 0x2c, 0x25, 0xa4, - 0x32, 0xc9, 0x16, 0x9a, 0x49, 0x5f, 0x5d, 0x33, 0xc5, 0x58, 0x33, 0xaa, 0xb2, 0xd0, 0xce, 0x21, - 0x2c, 0xc5, 0xda, 0x49, 0x20, 0x32, 0x79, 0x0e, 0xbc, 0x8f, 0x88, 0x8a, 0x91, 0x88, 0x62, 0x18, - 0x86, 0x6c, 0x58, 0x8e, 0x70, 0x62, 0xea, 0x18, 0x71, 0xc3, 0x43, 0x67, 0x5a, 0x82, 0xdd, 0xb9, - 0x00, 0x2c, 0xaa, 0x2e, 0xc6, 0x66, 0xe8, 0x93, 0x42, 0xd1, 0x34, 0x5b, 0xc4, 0x95, 0xa7, 0x8d, - 0x0b, 0x7a, 0xcc, 0x5f, 0x8c, 0x42, 0xbc, 0x43, 0x2a, 0x8f, 0x95, 0xec, 0xe6, 0xfa, 0x05, 0x08, - 0xe7, 0x2b, 0xc4, 0x88, 0xc7, 0x71, 0xca, 0x5e, 0x6a, 0xc1, 0xdf, 0xe3, 0x1b, 0x81, 0x06, 0xf1, - 0xd5, 0xc0, 0xd0, 0x23, 0xc8, 0x38, 0xb8, 0xcf, 0x74, 0xed, 0xad, 0x6f, 0x74, 0xea, 0x3e, 0x31, - 0x64, 0x46, 0x69, 0x17, 0x96, 0xcf, 0x2f, 0xda, 0xf0, 0x1c, 0x3c, 0x46, 0x55, 0x28, 0xc6, 0x07, - 0x99, 0xd9, 0xb5, 0x58, 0x37, 0xa4, 0x4e, 0x00, 0xe5, 0x8c, 0x1b, 0xd1, 0x91, 0xf6, 0xc4, 0x62, - 0x5d, 0xc1, 0x46, 0xe9, 0x67, 0x0d, 0xf2, 0xa7, 0x98, 0x43, 0x4f, 0x20, 0x75, 0x0d, 0xf7, 0x7f, - 0xca, 0xef, 0xa1, 0x67, 0x90, 0x16, 0xb2, 0x4c, 0x5d, 0x5d, 0x96, 0xa2, 0x4e, 0xe9, 0x7b, 0x0d, - 0x6e, 0x5d, 0xa8, 0x28, 0x71, 0x67, 0xda, 0x74, 0x74, 0x2d, 0x9f, 0x2e, 0x36, 0x1d, 0x35, 0x7b, - 0x62, 0xfb, 0x5a, 0x21, 0x4a, 0x28, 0xf5, 0x94, 0xa4, 0x30, 0x6b, 0x45, 0xc8, 0xac, 0xf4, 0x42, - 0x83, 0x5b, 0x2d, 0xdc, 0xc7, 0x36, 0x27, 0x23, 0x3c, 0x51, 0xf2, 0x8e, 0xf8, 0xa4, 0xf2, 0x6c, - 0x8c, 0xee, 0xc2, 0xe2, 0x99, 0x59, 0x84, 0x1f, 0x01, 0x46, 0xfe, 0xd4, 0x18, 0x50, 0x1b, 0xe6, - 0xa3, 0xdb, 0xf5, 0xca, 0x17, 0xfe, 0xac, 0xba, 0x58, 0xd1, 0x3a, 0xdc, 0x0c, 0xb0, 0xd8, 0x04, - 0x01, 0x76, 0x4c, 0x55, 0x9f, 0xf5, 0xc2, 0x33, 0xc2, 0x28, 0x44, 0xae, 0xc7, 0x22, 0xbc, 0xd5, - 0x2b, 0x75, 0x60, 0xa1, 0xe1, 0xd9, 0xfd, 0xa1, 0x38, 0x33, 0xe5, 0x87, 0x00, 0xfa, 0x00, 0xd2, - 0x3d, 0x7c, 0x24, 0x5b, 0xce, 0x6e, 0x96, 0x93, 0x12, 0x4d, 0xfc, 0x6a, 0x8c, 0x36, 0x2a, 0xed, - 0xc0, 0xf2, 0x98, 0x65, 0x0b, 0x0d, 0x8a, 0x06, 0x44, 0x12, 0x2a, 0xc2, 0xb4, 0x2f, 0x8a, 0x84, - 0xaf, 0x63, 0x84, 0x8b, 0xff, 0xb4, 0xe0, 0xe6, 0x29, 0x49, 0xb7, 0xb8, 0xc5, 0x87, 0x0c, 0x65, - 0x61, 0xb6, 0xb9, 0xb3, 0x5b, 0x6f, 0xec, 0x7e, 0x52, 0x98, 0x42, 0x39, 0x98, 0x3b, 0xd8, 0x31, - 0x1a, 0x8f, 0x1b, 0x3b, 0xf5, 0x82, 0x86, 0x00, 0x66, 0xb6, 0xb6, 0xdb, 0x8d, 0x83, 0x9d, 0x42, - 0x4a, 0x78, 0xf6, 0x77, 0x6b, 0x7b, 0xbb, 0xf5, 0x9d, 0x7a, 0x21, 0x8d, 0x66, 0x21, 0xbd, 0xb5, - 0xfb, 0x45, 0x21, 0x53, 0xdb, 0x7d, 0xf1, 0x7a, 0x45, 0x7b, 0xf9, 0x7a, 0x45, 0xfb, 0xfd, 0xf5, - 0x8a, 0xf6, 0xc3, 0x9b, 0x95, 0xa9, 0x97, 0x6f, 0x56, 0xa6, 0x7e, 0x7b, 0xb3, 0x32, 0xf5, 0xe5, - 0x25, 0x08, 0x1c, 0x27, 0xff, 0xb5, 0x24, 0x9b, 0x9d, 0x19, 0xf9, 0xf7, 0xf4, 0xe0, 0x8f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x69, 0x4c, 0x8a, 0x3d, 0x24, 0x0e, 0x00, 0x00, + 0x16, 0x36, 0x25, 0xf9, 0xef, 0x48, 0xb2, 0x95, 0x89, 0xd6, 0xab, 0xc4, 0x58, 0xdb, 0xab, 0xcd, + 0x06, 0x42, 0x1b, 0x4b, 0xb1, 0x13, 0xa0, 0x69, 0x8b, 0x16, 0xb0, 0x2c, 0xa5, 0x11, 0x9a, 0xd8, + 0x2a, 0x25, 0xbb, 0x3f, 0x40, 0xc1, 0x50, 0xe4, 0x98, 0x9a, 0x4a, 0xe2, 0xb0, 0x9c, 0x91, 0x2a, + 0x3f, 0x45, 0x5b, 0xa0, 0x0f, 0xd1, 0x07, 0xc8, 0x65, 0x1f, 0x20, 0x97, 0x41, 0xd0, 0x8b, 0xc2, + 0x17, 0x46, 0x91, 0xbc, 0x48, 0x31, 0xc3, 0x11, 0x49, 0xbb, 0x76, 0xea, 0xc4, 0xbe, 0xe3, 0x9c, + 0xbf, 0xef, 0xf0, 0x3b, 0xdf, 0xcc, 0x90, 0x70, 0xbb, 0x63, 0x76, 0x0e, 0xfb, 0xd4, 0xad, 0x74, + 0xb8, 0xc5, 0xb8, 0xd9, 0x23, 0xae, 0x53, 0x19, 0x6d, 0xc4, 0x56, 0x65, 0xcf, 0xa7, 0x9c, 0xa2, + 0x7f, 0xa9, 0xb8, 0x72, 0xcc, 0x33, 0xda, 0xb8, 0x99, 0x77, 0xa8, 0x43, 0x65, 0x44, 0x45, 0x3c, + 0x05, 0xc1, 0x37, 0x6f, 0x58, 0x94, 0x0d, 0x28, 0x33, 0x02, 0x47, 0xb0, 0x50, 0xae, 0x5b, 0xc1, + 0xaa, 0x12, 0x61, 0x75, 0x30, 0x37, 0x37, 0x2a, 0x27, 0xd0, 0x6e, 0xae, 0x9e, 0xdd, 0x95, 0x47, + 0x3d, 0x15, 0x70, 0x27, 0x16, 0x60, 0x75, 0xb1, 0xd5, 0xf3, 0x28, 0x71, 0xb9, 0xea, 0x3c, 0x32, + 0x04, 0xd1, 0xc5, 0x5f, 0x52, 0x90, 0x7b, 0x48, 0x5c, 0xb3, 0x4f, 0xf8, 0x61, 0xd3, 0xa7, 0x23, + 0x62, 0x63, 0x1f, 0xdd, 0x81, 0x94, 0x69, 0xdb, 0x7e, 0x41, 0x5b, 0xd3, 0x4a, 0xf3, 0xd5, 0xc2, + 0xcb, 0x67, 0xeb, 0x79, 0xd5, 0xe9, 0x96, 0x6d, 0xfb, 0x98, 0xb1, 0x16, 0xf7, 0x89, 0xeb, 0xe8, + 0x32, 0x0a, 0xd5, 0x21, 0x6d, 0x63, 0x66, 0xf9, 0xc4, 0xe3, 0x84, 0xba, 0x85, 0xc4, 0x9a, 0x56, + 0x4a, 0x6f, 0xfe, 0xaf, 0xac, 0x32, 0x22, 0x46, 0xe4, 0xdb, 0x94, 0x6b, 0x51, 0xa8, 0x1e, 0xcf, + 0x43, 0x4f, 0x00, 0x2c, 0x3a, 0x18, 0x10, 0xc6, 0x44, 0x95, 0xa4, 0x84, 0x5e, 0x3f, 0x3a, 0x5e, + 0x5d, 0x0e, 0x0a, 0x31, 0xbb, 0x57, 0x26, 0xb4, 0x32, 0x30, 0x79, 0xb7, 0xfc, 0x18, 0x3b, 0xa6, + 0x75, 0x58, 0xc3, 0xd6, 0xcb, 0x67, 0xeb, 0xa0, 0x70, 0x6a, 0xd8, 0xd2, 0x63, 0x05, 0xd0, 0x2e, + 0xcc, 0x74, 0xb8, 0x65, 0x78, 0xbd, 0x42, 0x6a, 0x4d, 0x2b, 0x65, 0xaa, 0x0f, 0x8e, 0x8e, 0x57, + 0xef, 0x3b, 0x84, 0x77, 0x87, 0x9d, 0xb2, 0x45, 0x07, 0x15, 0xc5, 0x52, 0xdf, 0xec, 0xb0, 0x75, + 0x42, 0x27, 0xcb, 0x0a, 0x3f, 0xf4, 0x30, 0x2b, 0x57, 0x1b, 0xcd, 0x7b, 0xf7, 0xef, 0x36, 0x87, + 0x9d, 0xcf, 0xf1, 0xa1, 0x3e, 0xdd, 0xe1, 0x56, 0xb3, 0x87, 0x3e, 0x81, 0xa4, 0x47, 0xbd, 0xc2, + 0xb4, 0x7c, 0xbd, 0xf7, 0xcb, 0x67, 0x0e, 0xbd, 0xdc, 0xf4, 0x29, 0x3d, 0xd8, 0x3d, 0x68, 0x52, + 0xc6, 0xb0, 0xec, 0xa3, 0xda, 0xde, 0xd6, 0x45, 0x1e, 0xba, 0x0f, 0x4b, 0xac, 0x6f, 0xb2, 0x2e, + 0xb6, 0x0d, 0x95, 0x6a, 0x74, 0x31, 0x71, 0xba, 0xbc, 0x30, 0xb3, 0xa6, 0x95, 0x52, 0x7a, 0x5e, + 0x79, 0xab, 0x81, 0xf3, 0x91, 0xf4, 0xa1, 0x3b, 0x80, 0xc2, 0x2c, 0x6e, 0x4d, 0x32, 0x66, 0xd7, + 0xb4, 0x52, 0x56, 0xcf, 0x4d, 0x32, 0xb8, 0xa5, 0xa2, 0x97, 0x60, 0xe6, 0x3b, 0x93, 0xf4, 0xb1, + 0x5d, 0x98, 0x5b, 0xd3, 0x4a, 0x73, 0xba, 0x5a, 0xa1, 0xbb, 0x90, 0xef, 0x12, 0xa7, 0x8b, 0x19, + 0x37, 0x46, 0x94, 0x63, 0x7b, 0x52, 0x67, 0x5e, 0xd6, 0x41, 0xca, 0xb7, 0x2f, 0x5c, 0x41, 0xa5, + 0xe2, 0xef, 0x09, 0x28, 0x9c, 0x96, 0xc5, 0x97, 0x84, 0x77, 0x9f, 0x60, 0x6e, 0xc6, 0xa8, 0xd5, + 0xae, 0x86, 0xda, 0x25, 0x98, 0x51, 0x1d, 0x25, 0x24, 0x17, 0x6a, 0x85, 0xfe, 0x0b, 0x99, 0x11, + 0xe5, 0xc4, 0x75, 0x0c, 0x8f, 0xfe, 0x80, 0x7d, 0x29, 0x8a, 0x94, 0x9e, 0x0e, 0x6c, 0x4d, 0x61, + 0x7a, 0x03, 0xad, 0xa9, 0xb7, 0xa6, 0x75, 0xfa, 0x1f, 0x69, 0x9d, 0xb9, 0x10, 0xad, 0xb3, 0xe7, + 0xd2, 0xfa, 0xdb, 0x2c, 0x64, 0xab, 0xed, 0xed, 0x1a, 0xee, 0x63, 0xc7, 0x94, 0xaa, 0xff, 0x10, + 0xd2, 0x42, 0x3e, 0xd8, 0x37, 0x2e, 0xb4, 0xe3, 0x20, 0x08, 0x16, 0xc6, 0xd8, 0x18, 0x12, 0x57, + 0xaa, 0xf0, 0xe4, 0x3b, 0x2a, 0xfc, 0x5b, 0x58, 0x38, 0xf0, 0x8c, 0xa0, 0x25, 0xa3, 0x4f, 0x98, + 0x18, 0x41, 0xf2, 0x52, 0x7d, 0xa5, 0x0f, 0xbc, 0xaa, 0xe8, 0xec, 0x31, 0x61, 0x52, 0x0c, 0xaa, + 0x0d, 0x83, 0x93, 0x01, 0x56, 0xd3, 0x4a, 0x2b, 0x5b, 0x9b, 0x0c, 0xb0, 0x0a, 0xf1, 0x79, 0x7c, + 0x67, 0x05, 0x21, 0x3e, 0x57, 0xb3, 0xfc, 0x0f, 0x00, 0x76, 0x4f, 0x4d, 0x6a, 0x1e, 0xbb, 0x6a, + 0x40, 0x68, 0x19, 0xe6, 0x39, 0xe5, 0x66, 0xdf, 0x60, 0x26, 0x97, 0x9b, 0x28, 0xa5, 0xcf, 0x49, + 0x43, 0xcb, 0x94, 0xb9, 0x61, 0x07, 0x63, 0xb9, 0x79, 0x32, 0xfa, 0xfc, 0x04, 0x7f, 0x2c, 0x45, + 0xa5, 0xdc, 0x74, 0xc8, 0xbd, 0x21, 0x37, 0x88, 0x3d, 0x2e, 0x80, 0x12, 0x55, 0xe0, 0xd9, 0x95, + 0x8e, 0x86, 0x3d, 0x46, 0x9b, 0x90, 0x96, 0x42, 0x53, 0xd5, 0xd2, 0x72, 0x84, 0xd7, 0x8e, 0x8e, + 0x57, 0x85, 0x40, 0x5a, 0xca, 0xd3, 0x1e, 0xeb, 0xc0, 0xc2, 0x67, 0xf4, 0x14, 0xb2, 0x76, 0x20, + 0x1d, 0xea, 0x1b, 0x8c, 0x38, 0x85, 0x8c, 0xcc, 0xfa, 0xf8, 0xe8, 0x78, 0xf5, 0x83, 0xb7, 0x23, + 0xb8, 0x45, 0x1c, 0xd7, 0xe4, 0x43, 0x1f, 0xeb, 0x99, 0xb0, 0x62, 0x8b, 0x38, 0x68, 0x0f, 0xb2, + 0x16, 0x1d, 0x61, 0xd7, 0x74, 0xb9, 0x00, 0x60, 0x85, 0xec, 0x5a, 0xb2, 0x94, 0xde, 0xbc, 0x7b, + 0x8e, 0x18, 0xb6, 0x55, 0xec, 0x96, 0x6d, 0x7a, 0x41, 0x85, 0xa0, 0x2a, 0xd3, 0x33, 0x93, 0x32, + 0x2d, 0xe2, 0x30, 0xf4, 0x7f, 0x58, 0x18, 0xba, 0x1d, 0xea, 0xda, 0xe1, 0xf4, 0x16, 0x24, 0x2d, + 0xd9, 0xd0, 0x2a, 0xe7, 0xf7, 0x05, 0xe4, 0x84, 0x7c, 0x86, 0xae, 0x1d, 0x6e, 0x90, 0xc2, 0xa2, + 0x54, 0xe3, 0xed, 0x73, 0x1a, 0xa8, 0xb6, 0xb7, 0xf7, 0x62, 0xd1, 0xfa, 0x62, 0x87, 0x5b, 0x71, + 0x83, 0x40, 0xf6, 0x4c, 0xdf, 0x1c, 0x30, 0x63, 0x84, 0x7d, 0x79, 0xb3, 0xe4, 0x02, 0xe4, 0xc0, + 0xba, 0x1f, 0x18, 0xd1, 0x2d, 0x58, 0x10, 0xc8, 0x9c, 0x78, 0x13, 0x69, 0x5c, 0x93, 0x61, 0x99, + 0x0e, 0xb7, 0xda, 0xc4, 0x53, 0xdb, 0xf7, 0x53, 0x58, 0xaa, 0x4d, 0xd8, 0xda, 0x9b, 0x74, 0xde, + 0x70, 0x0f, 0xa8, 0xc8, 0x67, 0x9e, 0x10, 0x96, 0xdc, 0x9f, 0x62, 0xa0, 0xf2, 0x68, 0xd4, 0x33, + 0xd2, 0xda, 0x12, 0xc6, 0xf6, 0xb8, 0xf8, 0x73, 0x0a, 0x16, 0x4f, 0x75, 0x2c, 0x34, 0x1b, 0xa3, + 0x66, 0x92, 0x97, 0x8e, 0x88, 0xf9, 0x9b, 0x54, 0x12, 0x17, 0x91, 0xca, 0xf7, 0xb0, 0x14, 0x93, + 0xca, 0x24, 0x5b, 0x68, 0x26, 0x79, 0x79, 0xcd, 0xe4, 0x23, 0xcd, 0xa8, 0xca, 0x42, 0x3b, 0x07, + 0xb0, 0x14, 0x69, 0x27, 0x86, 0xc8, 0xe4, 0x39, 0xf0, 0x2e, 0x22, 0xca, 0x87, 0x22, 0x8a, 0x60, + 0x18, 0xb2, 0x60, 0x39, 0xc4, 0x89, 0xa8, 0x63, 0xc4, 0x09, 0x0e, 0x9d, 0x69, 0x09, 0x76, 0xeb, + 0x1c, 0xb0, 0xb0, 0xba, 0x18, 0x9b, 0x5e, 0x98, 0x14, 0x0a, 0xa7, 0xd9, 0x22, 0x8e, 0x3c, 0x6d, + 0x1c, 0x28, 0x44, 0xfc, 0x45, 0x28, 0xc4, 0x3d, 0xa0, 0xf2, 0x58, 0x49, 0x6f, 0xae, 0x9f, 0x83, + 0x70, 0xb6, 0x42, 0xf4, 0x68, 0x1c, 0x27, 0xec, 0xc5, 0x16, 0xfc, 0x3b, 0xba, 0x11, 0xa8, 0x1f, + 0x5d, 0x0d, 0x0c, 0x3d, 0x80, 0x94, 0x8d, 0xfb, 0xac, 0xa0, 0xbd, 0xf1, 0x8d, 0x4e, 0xdc, 0x27, + 0xba, 0xcc, 0x28, 0xee, 0xc0, 0xf2, 0xd9, 0x45, 0x1b, 0xae, 0x8d, 0xc7, 0xa8, 0x02, 0xf9, 0xe8, + 0x20, 0x33, 0xba, 0x26, 0xeb, 0x06, 0xd4, 0x09, 0xa0, 0x8c, 0x7e, 0x2d, 0x3c, 0xd2, 0x1e, 0x99, + 0xac, 0x2b, 0xd8, 0x28, 0xfe, 0xaa, 0x41, 0xf6, 0x04, 0x73, 0xe8, 0x11, 0x24, 0xae, 0xe0, 0xfe, + 0x4f, 0x78, 0x3d, 0xf4, 0x04, 0x92, 0x42, 0x96, 0x89, 0xcb, 0xcb, 0x52, 0xd4, 0x29, 0xfe, 0xa8, + 0xc1, 0x8d, 0x73, 0x15, 0x25, 0xee, 0x4c, 0x8b, 0x8e, 0xae, 0xe4, 0xd3, 0xc5, 0xa2, 0xa3, 0x66, + 0x4f, 0x6c, 0x5f, 0x33, 0x40, 0x09, 0xa4, 0x9e, 0x90, 0x14, 0xa6, 0xcd, 0x10, 0x99, 0x15, 0x9f, + 0x6b, 0x70, 0xa3, 0x85, 0xfb, 0xd8, 0xe2, 0x64, 0x84, 0x27, 0x4a, 0xae, 0x8b, 0x4f, 0x2a, 0xd7, + 0xc2, 0xe8, 0x36, 0x2c, 0x9e, 0x9a, 0x45, 0xf0, 0x11, 0xa0, 0x67, 0x4f, 0x8c, 0x01, 0xb5, 0x61, + 0x3e, 0xbc, 0x5d, 0x2f, 0x7d, 0xe1, 0xcf, 0xaa, 0x8b, 0x15, 0xad, 0xc3, 0x75, 0x1f, 0x8b, 0x4d, + 0xe0, 0x63, 0xdb, 0x50, 0xf5, 0x59, 0x2f, 0x38, 0x23, 0xf4, 0x5c, 0xe8, 0x7a, 0x28, 0xc2, 0x5b, + 0xbd, 0x62, 0x07, 0x16, 0x1a, 0xae, 0xd5, 0x1f, 0x8a, 0x33, 0x53, 0x7e, 0x08, 0xa0, 0x8f, 0x20, + 0xd9, 0xc3, 0x87, 0xb2, 0xe5, 0xf4, 0x66, 0x29, 0x2e, 0xd1, 0xd8, 0xaf, 0xc6, 0x68, 0xa3, 0xdc, + 0xf6, 0x4d, 0x97, 0x99, 0x96, 0xd0, 0xa0, 0x68, 0x40, 0x24, 0xa1, 0x3c, 0x4c, 0x7b, 0xa2, 0x48, + 0xf0, 0x3a, 0x7a, 0xb0, 0x78, 0xef, 0x29, 0x5c, 0x3f, 0x21, 0xe9, 0x16, 0x37, 0xf9, 0x90, 0xa1, + 0x34, 0xcc, 0x36, 0xeb, 0x3b, 0xb5, 0xc6, 0xce, 0x67, 0xb9, 0x29, 0x94, 0x81, 0xb9, 0xfd, 0xba, + 0xde, 0x78, 0xd8, 0xa8, 0xd7, 0x72, 0x1a, 0x02, 0x98, 0xd9, 0xda, 0x6e, 0x37, 0xf6, 0xeb, 0xb9, + 0x84, 0xf0, 0xec, 0xed, 0x54, 0x77, 0x77, 0x6a, 0xf5, 0x5a, 0x2e, 0x29, 0x92, 0xea, 0x5f, 0x35, + 0x1b, 0x7a, 0xbd, 0x96, 0x4b, 0xa1, 0x59, 0x48, 0x6e, 0xed, 0x7c, 0x9d, 0x9b, 0xae, 0xee, 0x3c, + 0x7f, 0xb5, 0xa2, 0xbd, 0x78, 0xb5, 0xa2, 0xfd, 0xf9, 0x6a, 0x45, 0xfb, 0xe9, 0xf5, 0xca, 0xd4, + 0x8b, 0xd7, 0x2b, 0x53, 0x7f, 0xbc, 0x5e, 0x99, 0xfa, 0xe6, 0x02, 0x6c, 0x8e, 0xe3, 0x3f, 0x5e, + 0x92, 0xda, 0xce, 0x8c, 0xfc, 0x95, 0xba, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf6, 0xf0, + 0x69, 0x33, 0x31, 0x0e, 0x00, 0x00, } func (m *FinalityProvider) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/events.go b/x/btcstaking/types/events.go index 55a476e7e..73b087676 100644 --- a/x/btcstaking/types/events.go +++ b/x/btcstaking/types/events.go @@ -141,7 +141,7 @@ func NewExpiredDelegationEvent( ) *EventBTCDelegationExpired { return &EventBTCDelegationExpired{ StakingTxHash: stakingTxHash, - NewState: BTCDelegationStatus_UNBONDED.String(), + NewState: BTCDelegationStatus_EXPIRED.String(), } } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index d156a54e8..6dc2ae3cb 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -8,16 +8,22 @@ import ( "time" "cosmossdk.io/core/header" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + appparams "github.com/babylonlabs-io/babylon/app/params" "github.com/babylonlabs-io/babylon/testutil/datagen" + testutil "github.com/babylonlabs-io/babylon/testutil/incentives-helper" keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/types" + btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" epochingtypes "github.com/babylonlabs-io/babylon/x/epoching/types" "github.com/babylonlabs-io/babylon/x/finality/keeper" "github.com/babylonlabs-io/babylon/x/finality/types" + ictvtypes "github.com/babylonlabs-io/babylon/x/incentive/types" ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { @@ -486,3 +492,146 @@ func TestVerifyActivationHeight(t *testing.T) { blockHeight, activationHeight, ).Error()) } + +func TestBtcDelegationRewards(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + btcLightClientTipHeight := uint32(30) + + btclcKeeper := bstypes.NewMockBTCLightClientKeeper(ctrl) + btccKForBtcStaking := bstypes.NewMockBtcCheckpointKeeper(ctrl) + + epochNumber := uint64(10) + btccKForFinality := types.NewMockCheckpointingKeeper(ctrl) + btccKForFinality.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epochNumber}).AnyTimes() + btccKForFinality.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(epochNumber).AnyTimes() + + h := testutil.NewIncentiveHelper(t, btclcKeeper, btccKForBtcStaking, btccKForFinality) + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + h.SetFinalityActivationHeight(0) + + // generate and insert new finality provider + stakingValueFp1Del1 := int64(2 * 10e8) + stakingValueFp1Del2 := int64(4 * 10e8) + stakingTime := uint16(1000) + + fp1SK, fp1PK, fp1 := h.CreateFinalityProvider(r) + // commit some public randomness + startHeight := uint64(0) + h.FpAddPubRand(r, fp1SK, startHeight) + + _, fp1Del1, _ := h.CreateActiveBtcDelegation(r, covenantSKs, fp1PK, stakingValueFp1Del1, stakingTime, btcLightClientTipHeight) + _, fp1Del2, _ := h.CreateActiveBtcDelegation(r, covenantSKs, fp1PK, stakingValueFp1Del2, stakingTime, btcLightClientTipHeight) + + // process the events of the activated BTC delegations + h.BTCStakingKeeper.IndexBTCHeight(h.Ctx) + h.FinalityKeeper.UpdatePowerDist(h.Ctx) + + fp1CurrentRwd, err := h.IncentivesKeeper.GetFinalityProviderCurrentRewards(h.Ctx, fp1.Address()) + h.NoError(err) + h.Equal(stakingValueFp1Del1+stakingValueFp1Del2, fp1CurrentRwd.TotalActiveSat.Int64()) + + fp1Del1RwdTracker, err := h.IncentivesKeeper.GetBTCDelegationRewardsTracker(h.Ctx, fp1.Address(), fp1Del1.Address()) + h.NoError(err) + h.Equal(stakingValueFp1Del1, fp1Del1RwdTracker.TotalActiveSat.Int64()) + + fp1Del2RwdTracker, err := h.IncentivesKeeper.GetBTCDelegationRewardsTracker(h.Ctx, fp1.Address(), fp1Del2.Address()) + h.NoError(err) + h.Equal(stakingValueFp1Del2, fp1Del2RwdTracker.TotalActiveSat.Int64()) + + // fp1, del1 => 2_00000000 + // fp1, del2 => 4_00000000 + + // if 1500ubbn are added as reward + // del1 should receive 1/3 => 500 + // del2 should receive 2/3 => 1000 + rwdFp1 := sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(1500))) + err = h.IncentivesKeeper.AddFinalityProviderRewardsForBtcDelegations(h.Ctx, fp1.Address(), rwdFp1) + h.NoError(err) + + rwdFp1Del1 := sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(500))) + rwdFp1Del2 := sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(1000))) + + fp1Del1Rwd, err := h.IncentivesKeeper.RewardGauges(h.Ctx, &ictvtypes.QueryRewardGaugesRequest{ + Address: fp1Del1.Address().String(), + }) + h.NoError(err) + h.Equal(fp1Del1Rwd.RewardGauges[ictvtypes.BTCDelegationType.String()].Coins.String(), rwdFp1Del1.String()) + + fp1Del2Rwd, err := h.IncentivesKeeper.RewardGauges(h.Ctx, &ictvtypes.QueryRewardGaugesRequest{ + Address: fp1Del2.Address().String(), + }) + h.NoError(err) + h.Equal(fp1Del2Rwd.RewardGauges[ictvtypes.BTCDelegationType.String()].Coins.String(), rwdFp1Del2.String()) +} + +func TestBtcDelegationRewardsEarlyUnbondingAndExpire(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + btclcKeeper := bstypes.NewMockBTCLightClientKeeper(ctrl) + btccKForBtcStaking := bstypes.NewMockBtcCheckpointKeeper(ctrl) + + epochNumber := uint64(10) + btccKForFinality := types.NewMockCheckpointingKeeper(ctrl) + btccKForFinality.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epochNumber}).AnyTimes() + btccKForFinality.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(epochNumber).AnyTimes() + + h := testutil.NewIncentiveHelper(t, btclcKeeper, btccKForBtcStaking, btccKForFinality) + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + h.SetFinalityActivationHeight(0) + + // generate and insert new finality provider + stakingValue := int64(2 * 10e8) + stakingTime := uint16(1001) + + fpSK, fpPK, fp := h.CreateFinalityProvider(r) + // commit some public randomness + startHeight := uint64(0) + h.FpAddPubRand(r, fpSK, startHeight) + btcLightClientTipHeight := uint32(30) + + stakingTxHash, del, unbondingInfo := h.CreateActiveBtcDelegation(r, covenantSKs, fpPK, stakingValue, stakingTime, btcLightClientTipHeight) + + // process the events as active btc delegation + h.BTCStakingKeeper.IndexBTCHeight(h.Ctx) + h.FinalityKeeper.UpdatePowerDist(h.Ctx) + + h.EqualBtcDelRwdTrackerActiveSat(fp.Address(), del.Address(), uint64(stakingValue)) + + // Execute early unbonding + btcLightClientTipHeight = uint32(45) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(&btclctypes.BTCHeaderInfo{Height: btcLightClientTipHeight}).AnyTimes() + + h.BtcUndelegate(stakingTxHash, del, unbondingInfo, btcLightClientTipHeight) + + // increases one bbn block to get the voting power distribution cache + // from the previous block + h.CtxAddBlkHeight(1) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(&btclctypes.BTCHeaderInfo{Height: btcLightClientTipHeight}).AnyTimes() + + // process the events as early unbonding btc delegation + h.BTCStakingKeeper.IndexBTCHeight(h.Ctx) + h.FinalityKeeper.UpdatePowerDist(h.Ctx) + + h.EqualBtcDelRwdTrackerActiveSat(fp.Address(), del.Address(), 0) + + // reaches the btc block of expire BTC delegation + // an unbond event will be processed + // but should not reduce the TotalActiveSat again + h.CtxAddBlkHeight(1) + + btcLightClientTipHeight += uint32(stakingTime) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(&btclctypes.BTCHeaderInfo{Height: btcLightClientTipHeight}).AnyTimes() + + // process the events as expired btc delegation + h.BTCStakingKeeper.IndexBTCHeight(h.Ctx) + h.FinalityKeeper.UpdatePowerDist(h.Ctx) + + h.EqualBtcDelRwdTrackerActiveSat(fp.Address(), del.Address(), 0) +} diff --git a/x/finality/keeper/power_dist_change.go b/x/finality/keeper/power_dist_change.go index 8873851d6..581d415a0 100644 --- a/x/finality/keeper/power_dist_change.go +++ b/x/finality/keeper/power_dist_change.go @@ -173,10 +173,12 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( events []*types.EventPowerDistUpdate, ) *ftypes.VotingPowerDistCache { // a map where key is finality provider's BTC PK hex and value is a list - // of BTC delegations that newly become active under this provider - activeBTCDels := map[string][]*types.BTCDelegation{} - // a map where key is unbonded BTC delegation's staking tx hash - unbondedBTCDels := map[string]struct{}{} + // of BTC delegations satoshis amount that newly become active under this provider + activedSatsByFpBtcPk := map[string][]uint64{} + // a map where key is finality provider's BTC PK hex and value is a list + // of BTC delegations satoshis that were unbonded or expired without previously + // being unbonded + unbondedSatsByFpBtcPk := map[string][]uint64{} // a map where key is slashed finality providers' BTC PK slashedFPs := map[string]struct{}{} // a map where key is jailed finality providers' BTC PK @@ -184,6 +186,9 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( // a map where key is unjailed finality providers' BTC PK unjailedFPs := map[string]struct{}{} + // simple cache to load fp by his btc pk hex + fpByBtcPkHex := map[string]*types.FinalityProvider{} + /* filter and classify all events into new/expired BTC delegations and jailed/slashed FPs */ @@ -192,29 +197,46 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( switch typedEvent := event.Ev.(type) { case *types.EventPowerDistUpdate_BtcDelStateUpdate: delEvent := typedEvent.BtcDelStateUpdate - btcDel, err := k.BTCStakingKeeper.GetBTCDelegation(ctx, delEvent.StakingTxHash) + delStkTxHash := delEvent.StakingTxHash + + btcDel, err := k.BTCStakingKeeper.GetBTCDelegation(ctx, delStkTxHash) if err != nil { panic(err) // only programming error } - if delEvent.NewState == types.BTCDelegationStatus_ACTIVE { + + switch delEvent.NewState { + case types.BTCDelegationStatus_ACTIVE: // newly active BTC delegation // add the BTC delegation to each restaked finality provider for _, fpBTCPK := range btcDel.FpBtcPkList { fpBTCPKHex := fpBTCPK.MarshalHex() - activeBTCDels[fpBTCPKHex] = append(activeBTCDels[fpBTCPKHex], btcDel) + activedSatsByFpBtcPk[fpBTCPKHex] = append(activedSatsByFpBtcPk[fpBTCPKHex], btcDel.TotalSat) } - } else if delEvent.NewState == types.BTCDelegationStatus_UNBONDED { - // emit expired event if it is not early unbonding + + k.processRewardTracker(ctx, fpByBtcPkHex, btcDel, func(fp, del sdk.AccAddress, sats uint64) { + k.MustProcessBtcDelegationActivated(ctx, fp, del, sats) + }) + case types.BTCDelegationStatus_UNBONDED: + // add the unbonded BTC delegation to the map + k.processPowerDistUpdateEventUnbond(ctx, fpByBtcPkHex, btcDel, unbondedSatsByFpBtcPk) + case types.BTCDelegationStatus_EXPIRED: + types.EmitExpiredDelegationEvent(sdkCtx, delStkTxHash) + if !btcDel.IsUnbondedEarly() { - types.EmitExpiredDelegationEvent(sdkCtx, delEvent.StakingTxHash) + // only adds to the new unbonded list if it hasn't + // previously unbonded with types.BTCDelegationStatus_UNBONDED + k.processPowerDistUpdateEventUnbond(ctx, fpByBtcPkHex, btcDel, unbondedSatsByFpBtcPk) } - // add the unbonded BTC delegation to the map - unbondedBTCDels[delEvent.StakingTxHash] = struct{}{} } case *types.EventPowerDistUpdate_SlashedFp: // record slashed fps types.EmitSlashedFPEvent(sdkCtx, typedEvent.SlashedFp.Pk) - slashedFPs[typedEvent.SlashedFp.Pk.MarshalHex()] = struct{}{} + fpBTCPKHex := typedEvent.SlashedFp.Pk.MarshalHex() + slashedFPs[fpBTCPKHex] = struct{}{} + fp := k.loadFP(ctx, fpByBtcPkHex, fpBTCPKHex) + if err := k.IncentiveKeeper.FpSlashed(ctx, fp.Address()); err != nil { + panic(err) + } case *types.EventPowerDistUpdate_JailedFp: // record jailed fps types.EmitJailedFPEvent(sdkCtx, typedEvent.JailedFp.Pk) @@ -230,24 +252,21 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( Then, construct a voting power dist cache by reconciling the previous cache and all the new events. */ - // TODO: the algorithm needs to iterate over all BTC delegations so remains + // TODO: the algorithm needs to iterate over all the finality providers so remains // sub-optimal. Ideally we only need to iterate over all events above rather - // than the entire cache. This is made difficulty since BTC delegations are - // not keyed in the cache. Need to find a way to optimise this. + // than the entire cache. newDc := ftypes.NewVotingPowerDistCache() // iterate over all finality providers and apply all events for i := range dc.FinalityProviders { // create a copy of the finality provider fp := *dc.FinalityProviders[i] - fp.TotalBondedSat = 0 - fp.BtcDels = []*ftypes.BTCDelDistInfo{} - fpBTCPKHex := fp.BtcPk.MarshalHex() // if this finality provider is slashed, continue to avoid // assigning delegation to it - if _, ok := slashedFPs[fpBTCPKHex]; ok { + _, isSlashed := slashedFPs[fpBTCPKHex] + if isSlashed { fp.IsSlashed = true continue } @@ -264,24 +283,29 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( fp.IsJailed = false } - // add all BTC delegations that are not unbonded to the new finality provider - for j := range dc.FinalityProviders[i].BtcDels { - btcDel := *dc.FinalityProviders[i].BtcDels[j] - if _, ok := unbondedBTCDels[btcDel.StakingTxHash]; !ok { - fp.AddBTCDelDistInfo(&btcDel) - } - } - // process all new BTC delegations under this finality provider - if fpActiveBTCDels, ok := activeBTCDels[fpBTCPKHex]; ok { + if fpActiveSats, ok := activedSatsByFpBtcPk[fpBTCPKHex]; ok { // handle new BTC delegations for this finality provider - for _, d := range fpActiveBTCDels { - fp.AddBTCDel(d) + for _, activatedSats := range fpActiveSats { + fp.AddBondedSats(activatedSats) } - // remove the finality provider entry in activeBTCDels map, so that - // after the for loop the rest entries in activeBTCDels belongs to new + // remove the finality provider entry in fpActiveSats map, so that + // after the for loop the rest entries in fpActiveSats belongs to new // finality providers with new BTC delegations - delete(activeBTCDels, fpBTCPKHex) + delete(activedSatsByFpBtcPk, fpBTCPKHex) + } + + // process all new unbonding BTC delegations under this finality provider + if fpUnbondedSats, ok := unbondedSatsByFpBtcPk[fpBTCPKHex]; ok { + // handle unbonded delegations for this finality provider + for _, unbodedSats := range fpUnbondedSats { + fp.RemoveBondedSats(unbodedSats) + } + // remove the finality provider entry in fpUnbondedSats map, so that + // after the for loop the rest entries in fpUnbondedSats belongs to new + // finality providers that might have btc delegations entries + // that activated and unbonded in the same slice of events + delete(unbondedSatsByFpBtcPk, fpBTCPKHex) } // add this finality provider to the new cache if it has voting power @@ -294,30 +318,32 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( process new BTC delegations under new finality providers in activeBTCDels */ // sort new finality providers in activeBTCDels to ensure determinism - fpBTCPKHexList := make([]string, 0, len(activeBTCDels)) - for fpBTCPKHex := range activeBTCDels { - fpBTCPKHexList = append(fpBTCPKHexList, fpBTCPKHex) + fpActiveBtcPkHexList := make([]string, 0, len(activedSatsByFpBtcPk)) + for fpBTCPKHex := range activedSatsByFpBtcPk { + fpActiveBtcPkHexList = append(fpActiveBtcPkHexList, fpBTCPKHex) } - sort.SliceStable(fpBTCPKHexList, func(i, j int) bool { - return fpBTCPKHexList[i] < fpBTCPKHexList[j] + sort.SliceStable(fpActiveBtcPkHexList, func(i, j int) bool { + return fpActiveBtcPkHexList[i] < fpActiveBtcPkHexList[j] }) + // for each new finality provider, apply the new BTC delegations to the new dist cache - for _, fpBTCPKHex := range fpBTCPKHexList { + for _, fpBTCPKHex := range fpActiveBtcPkHexList { // get the finality provider and initialise its dist info - fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(fpBTCPKHex) - if err != nil { - panic(err) // only programming error - } - newFP, err := k.BTCStakingKeeper.GetFinalityProvider(ctx, *fpBTCPK) - if err != nil { - panic(err) // only programming error - } + newFP := k.loadFP(ctx, fpByBtcPkHex, fpBTCPKHex) fpDistInfo := ftypes.NewFinalityProviderDistInfo(newFP) // add each BTC delegation - fpActiveBTCDels := activeBTCDels[fpBTCPKHex] - for _, d := range fpActiveBTCDels { - fpDistInfo.AddBTCDel(d) + fpActiveSats := activedSatsByFpBtcPk[fpBTCPKHex] + for _, activatedSats := range fpActiveSats { + fpDistInfo.AddBondedSats(activatedSats) + } + + // edge case where we might be processing an unbonded event + // from a newly active finality provider in the same slice + // of events received. + fpUnbondedSats := unbondedSatsByFpBtcPk[fpBTCPKHex] + for _, unbodedSats := range fpUnbondedSats { + fpDistInfo.RemoveBondedSats(unbodedSats) } // add this finality provider to the new cache if it has voting power @@ -329,6 +355,21 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( return newDc } +func (k Keeper) processPowerDistUpdateEventUnbond( + ctx context.Context, + cacheFpByBtcPkHex map[string]*types.FinalityProvider, + btcDel *types.BTCDelegation, + unbondedSatsByFpBtcPk map[string][]uint64, +) { + for _, fpBTCPK := range btcDel.FpBtcPkList { + fpBTCPKHex := fpBTCPK.MarshalHex() + unbondedSatsByFpBtcPk[fpBTCPKHex] = append(unbondedSatsByFpBtcPk[fpBTCPKHex], btcDel.TotalSat) + } + k.processRewardTracker(ctx, cacheFpByBtcPkHex, btcDel, func(fp, del sdk.AccAddress, sats uint64) { + k.MustProcessBtcDelegationUnbonded(ctx, fp, del, sats) + }) +} + func (k Keeper) SetVotingPowerDistCache(ctx context.Context, height uint64, dc *ftypes.VotingPowerDistCache) { store := k.votingPowerDistCacheStore(ctx) store.Set(sdk.Uint64ToBigEndian(height), k.cdc.MustMarshal(dc)) @@ -358,3 +399,58 @@ func (k Keeper) votingPowerDistCacheStore(ctx context.Context) prefix.Store { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) return prefix.NewStore(storeAdapter, ftypes.VotingPowerDistCacheKey) } + +// processRewardTracker loads the fps from inside the btc delegation +// with cache and executes the function by passing the fp, delegator address +// and satoshi amounts. +func (k Keeper) processRewardTracker( + ctx context.Context, + fpByBtcPkHex map[string]*types.FinalityProvider, + btcDel *types.BTCDelegation, + f func(fp, del sdk.AccAddress, sats uint64), +) { + delAddr := sdk.MustAccAddressFromBech32(btcDel.StakerAddr) + for _, fpBTCPK := range btcDel.FpBtcPkList { + fp := k.loadFP(ctx, fpByBtcPkHex, fpBTCPK.MarshalHex()) + f(fp.Address(), delAddr, btcDel.TotalSat) + } +} + +// MustProcessBtcDelegationActivated calls the IncentiveKeeper.BtcDelegationActivated +// and panics if it errors +func (k Keeper) MustProcessBtcDelegationActivated(ctx context.Context, fp, del sdk.AccAddress, sats uint64) { + err := k.IncentiveKeeper.BtcDelegationActivated(ctx, fp, del, sats) + if err != nil { + panic(err) + } +} + +// MustProcessBtcDelegationUnbonded calls the IncentiveKeeper.BtcDelegationUnbonded +// and panics if it errors +func (k Keeper) MustProcessBtcDelegationUnbonded(ctx context.Context, fp, del sdk.AccAddress, sats uint64) { + err := k.IncentiveKeeper.BtcDelegationUnbonded(ctx, fp, del, sats) + if err != nil { + panic(err) + } +} + +func (k Keeper) loadFP( + ctx context.Context, + cacheFpByBtcPkHex map[string]*types.FinalityProvider, + fpBTCPKHex string, +) *types.FinalityProvider { + fp, found := cacheFpByBtcPkHex[fpBTCPKHex] + if !found { + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(fpBTCPKHex) + if err != nil { + panic(err) // only programming error + } + fp, err = k.BTCStakingKeeper.GetFinalityProvider(ctx, *fpBTCPK) + if err != nil { + panic(err) // only programming error + } + cacheFpByBtcPkHex[fpBTCPKHex] = fp + } + + return fp +} diff --git a/x/finality/keeper/power_dist_change_test.go b/x/finality/keeper/power_dist_change_test.go index 932718f62..fa5a81c40 100644 --- a/x/finality/keeper/power_dist_change_test.go +++ b/x/finality/keeper/power_dist_change_test.go @@ -83,6 +83,62 @@ func FuzzProcessAllPowerDistUpdateEvents_Determinism(f *testing.F) { }) } +func FuzzProcessAllPowerDistUpdateEvents_ActiveAndUnbondTogether(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := testutil.NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + h.GenAndApplyParams(r) + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // empty dist cache + dc := ftypes.NewVotingPowerDistCache() + + _, fpPK, _ := h.CreateFinalityProvider(r) + + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + h.NoError(err) + _, _, del, _, _, _, err := h.CreateDelegationWithBtcBlockHeight( + r, + delSK, + fpPK, + changeAddress.EncodeAddress(), + int64(2*10e8), + 1000, + 0, + 0, + false, + false, + 10, + 30, + ) + h.NoError(err) + + eventActive := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ + StakingTxHash: del.MustGetStakingTxHash().String(), + NewState: types.BTCDelegationStatus_ACTIVE, + }) + eventUnbond := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{ + StakingTxHash: del.MustGetStakingTxHash().String(), + NewState: types.BTCDelegationStatus_UNBONDED, + }) + events := []*types.EventPowerDistUpdate{eventActive, eventUnbond} + + newDc := h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, dc, events) + require.Zero(t, newDc.TotalVotingPower) + }) +} + func FuzzSlashFinalityProviderEvent(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) @@ -421,6 +477,7 @@ func FuzzBTCDelegationEvents_NoPreApproval(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() r := rand.New(rand.NewSource(seed)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -469,14 +526,14 @@ func FuzzBTCDelegationEvents_NoPreApproval(f *testing.F) { events := h.BTCStakingKeeper.GetAllPowerDistUpdateEvents(h.Ctx, btcTip.Height, btcTip.Height) require.Len(t, events, 0) - // the BTC delegation will be unbonded at end height - unbonding_time + // the BTC delegation will be expired at end height - unbonding_time unbondedHeight := actualDel.EndHeight - stakingParams.UnbondingTimeBlocks events = h.BTCStakingKeeper.GetAllPowerDistUpdateEvents(h.Ctx, unbondedHeight, unbondedHeight) require.Len(t, events, 1) btcDelStateUpdate := events[0].GetBtcDelStateUpdate() require.NotNil(t, btcDelStateUpdate) require.Equal(t, stakingTxHash, btcDelStateUpdate.StakingTxHash) - require.Equal(t, types.BTCDelegationStatus_UNBONDED, btcDelStateUpdate.NewState) + require.Equal(t, types.BTCDelegationStatus_EXPIRED, btcDelStateUpdate.NewState) // ensure this finality provider does not have voting power at the current height babylonHeight := datagen.RandomInt(r, 10) + 1 @@ -625,7 +682,7 @@ func FuzzBTCDelegationEvents_WithPreApproval(f *testing.F) { btcDelStateUpdate = events[0].GetBtcDelStateUpdate() require.NotNil(t, btcDelStateUpdate) require.Equal(t, stakingTxHash, btcDelStateUpdate.StakingTxHash) - require.Equal(t, types.BTCDelegationStatus_UNBONDED, btcDelStateUpdate.NewState) + require.Equal(t, types.BTCDelegationStatus_EXPIRED, btcDelStateUpdate.NewState) // ensure this finality provider does not have voting power at the current height // due to no timestamped randomness @@ -664,6 +721,7 @@ func FuzzBTCDelegationEvents_WithPreApproval(f *testing.F) { } func TestDoNotGenerateDuplicateEventsAfterHavingCovenantQuorum(t *testing.T) { + t.Parallel() r := rand.New(rand.NewSource(time.Now().UnixNano())) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -710,14 +768,14 @@ func TestDoNotGenerateDuplicateEventsAfterHavingCovenantQuorum(t *testing.T) { events := h.BTCStakingKeeper.GetAllPowerDistUpdateEvents(h.Ctx, btcTip.Height, btcTip.Height) require.Len(t, events, 0) - // the BTC delegation will be unbonded at end height - unbonding_time + // the BTC delegation will be expired (unbonded) at end height - unbonding_time unbondedHeight := actualDel.EndHeight - stakingParams.UnbondingTimeBlocks events = h.BTCStakingKeeper.GetAllPowerDistUpdateEvents(h.Ctx, unbondedHeight, unbondedHeight) require.Len(t, events, 1) btcDelStateUpdate := events[0].GetBtcDelStateUpdate() require.NotNil(t, btcDelStateUpdate) require.Equal(t, expectedStakingTxHash, btcDelStateUpdate.StakingTxHash) - require.Equal(t, types.BTCDelegationStatus_UNBONDED, btcDelStateUpdate.NewState) + require.Equal(t, types.BTCDelegationStatus_EXPIRED, btcDelStateUpdate.NewState) // ensure this finality provider does not have voting power at the current height babylonHeight := datagen.RandomInt(r, 10) + 1 diff --git a/x/finality/keeper/power_table_test.go b/x/finality/keeper/power_table_test.go index f3a67b352..2b2d14520 100644 --- a/x/finality/keeper/power_table_test.go +++ b/x/finality/keeper/power_table_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "fmt" "math/rand" "sort" "testing" @@ -245,10 +246,6 @@ func FuzzRecordVotingPowerDistCache(f *testing.F) { fp, ok := fpsWithVotingPowerMap[sdk.AccAddress(fpDistInfo.Addr).String()] require.True(t, ok) require.Equal(t, fpDistInfo.Commission, fp.Commission) - require.Len(t, fpDistInfo.BtcDels, int(numBTCDels)) - for _, delDistInfo := range fpDistInfo.BtcDels { - require.Equal(t, delDistInfo.TotalSat, stakingValue) - } } }) } @@ -535,7 +532,7 @@ func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { }) for i := 0; i < numActiveFPs; i++ { votingPower := h.FinalityKeeper.GetVotingPower(h.Ctx, *fpsWithMeta[i].BtcPk, babylonHeight) - require.Equal(t, fpsWithMeta[i].VotingPower, votingPower) + require.Equal(t, fmt.Sprintf("%d", fpsWithMeta[i].VotingPower), fmt.Sprintf("%d", votingPower)) } for i := numActiveFPs; i < int(numFps); i++ { votingPower := h.FinalityKeeper.GetVotingPower(h.Ctx, *fpsWithMeta[i].BtcPk, babylonHeight) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index b42441742..7eae51243 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -34,4 +34,7 @@ type CheckpointingKeeper interface { type IncentiveKeeper interface { RewardBTCStaking(ctx context.Context, height uint64, filteredDc *VotingPowerDistCache) IndexRefundableMsg(ctx context.Context, msg sdk.Msg) + BtcDelegationActivated(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error + BtcDelegationUnbonded(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error + FpSlashed(ctx context.Context, fp sdk.AccAddress) error } diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 1f7c74471..f8daf23d4 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -88,18 +88,16 @@ type FinalityProviderDistInfo struct { Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` // total_bonded_sat is the total amount of bonded BTC stake (in Satoshi) of the finality provider TotalBondedSat uint64 `protobuf:"varint,4,opt,name=total_bonded_sat,json=totalBondedSat,proto3" json:"total_bonded_sat,omitempty"` - // btc_dels is a list of BTC delegations' voting power information under this finality provider - BtcDels []*BTCDelDistInfo `protobuf:"bytes,5,rep,name=btc_dels,json=btcDels,proto3" json:"btc_dels,omitempty"` // is_timestamped indicates whether the finality provider // has timestamped public randomness committed // if no, it should not be assigned voting power - IsTimestamped bool `protobuf:"varint,6,opt,name=is_timestamped,json=isTimestamped,proto3" json:"is_timestamped,omitempty"` + IsTimestamped bool `protobuf:"varint,5,opt,name=is_timestamped,json=isTimestamped,proto3" json:"is_timestamped,omitempty"` // is_jailed indicates whether the finality provider // is jailed, if so, it should not be assigned voting power - IsJailed bool `protobuf:"varint,7,opt,name=is_jailed,json=isJailed,proto3" json:"is_jailed,omitempty"` + IsJailed bool `protobuf:"varint,6,opt,name=is_jailed,json=isJailed,proto3" json:"is_jailed,omitempty"` // is_slashed indicates whether the finality provider // is slashed, if so, it should not be assigned voting power - IsSlashed bool `protobuf:"varint,8,opt,name=is_slashed,json=isSlashed,proto3" json:"is_slashed,omitempty"` + IsSlashed bool `protobuf:"varint,7,opt,name=is_slashed,json=isSlashed,proto3" json:"is_slashed,omitempty"` } func (m *FinalityProviderDistInfo) Reset() { *m = FinalityProviderDistInfo{} } @@ -149,13 +147,6 @@ func (m *FinalityProviderDistInfo) GetTotalBondedSat() uint64 { return 0 } -func (m *FinalityProviderDistInfo) GetBtcDels() []*BTCDelDistInfo { - if m != nil { - return m.BtcDels - } - return nil -} - func (m *FinalityProviderDistInfo) GetIsTimestamped() bool { if m != nil { return m.IsTimestamped @@ -177,73 +168,6 @@ func (m *FinalityProviderDistInfo) GetIsSlashed() bool { return false } -// BTCDelDistInfo contains the information related to voting power distribution for a BTC delegation -type BTCDelDistInfo struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - BtcPk *github_com_babylonlabs_io_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonlabs-io/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // staker_addr is the address to receive rewards from BTC delegation. - StakerAddr string `protobuf:"bytes,2,opt,name=staker_addr,json=stakerAddr,proto3" json:"staker_addr,omitempty"` - // staking_tx_hash is the staking tx hash of the BTC delegation - StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // total_sat is the amount of BTC stake (in Satoshi) of the BTC delegation - TotalSat uint64 `protobuf:"varint,4,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` -} - -func (m *BTCDelDistInfo) Reset() { *m = BTCDelDistInfo{} } -func (m *BTCDelDistInfo) String() string { return proto.CompactTextString(m) } -func (*BTCDelDistInfo) ProtoMessage() {} -func (*BTCDelDistInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{2} -} -func (m *BTCDelDistInfo) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *BTCDelDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_BTCDelDistInfo.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *BTCDelDistInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCDelDistInfo.Merge(m, src) -} -func (m *BTCDelDistInfo) XXX_Size() int { - return m.Size() -} -func (m *BTCDelDistInfo) XXX_DiscardUnknown() { - xxx_messageInfo_BTCDelDistInfo.DiscardUnknown(m) -} - -var xxx_messageInfo_BTCDelDistInfo proto.InternalMessageInfo - -func (m *BTCDelDistInfo) GetStakerAddr() string { - if m != nil { - return m.StakerAddr - } - return "" -} - -func (m *BTCDelDistInfo) GetStakingTxHash() string { - if m != nil { - return m.StakingTxHash - } - return "" -} - -func (m *BTCDelDistInfo) GetTotalSat() uint64 { - if m != nil { - return m.TotalSat - } - return 0 -} - // IndexedBlock is the necessary metadata and finalization status of a block type IndexedBlock struct { // height is the height of the block @@ -259,7 +183,7 @@ func (m *IndexedBlock) Reset() { *m = IndexedBlock{} } func (m *IndexedBlock) String() string { return proto.CompactTextString(m) } func (*IndexedBlock) ProtoMessage() {} func (*IndexedBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{3} + return fileDescriptor_ca5b87e52e3e6d02, []int{2} } func (m *IndexedBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -328,7 +252,7 @@ func (m *PubRandCommit) Reset() { *m = PubRandCommit{} } func (m *PubRandCommit) String() string { return proto.CompactTextString(m) } func (*PubRandCommit) ProtoMessage() {} func (*PubRandCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{4} + return fileDescriptor_ca5b87e52e3e6d02, []int{3} } func (m *PubRandCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -412,7 +336,7 @@ func (m *Evidence) Reset() { *m = Evidence{} } func (m *Evidence) String() string { return proto.CompactTextString(m) } func (*Evidence) ProtoMessage() {} func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{5} + return fileDescriptor_ca5b87e52e3e6d02, []int{4} } func (m *Evidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -480,7 +404,7 @@ func (m *FinalityProviderSigningInfo) Reset() { *m = FinalityProviderSig func (m *FinalityProviderSigningInfo) String() string { return proto.CompactTextString(m) } func (*FinalityProviderSigningInfo) ProtoMessage() {} func (*FinalityProviderSigningInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{6} + return fileDescriptor_ca5b87e52e3e6d02, []int{5} } func (m *FinalityProviderSigningInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -533,7 +457,6 @@ func (m *FinalityProviderSigningInfo) GetJailedUntil() time.Time { func init() { proto.RegisterType((*VotingPowerDistCache)(nil), "babylon.finality.v1.VotingPowerDistCache") proto.RegisterType((*FinalityProviderDistInfo)(nil), "babylon.finality.v1.FinalityProviderDistInfo") - proto.RegisterType((*BTCDelDistInfo)(nil), "babylon.finality.v1.BTCDelDistInfo") proto.RegisterType((*IndexedBlock)(nil), "babylon.finality.v1.IndexedBlock") proto.RegisterType((*PubRandCommit)(nil), "babylon.finality.v1.PubRandCommit") proto.RegisterType((*Evidence)(nil), "babylon.finality.v1.Evidence") @@ -545,71 +468,65 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 1019 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0x26, 0x4e, 0x6c, 0x8f, 0xed, 0xb4, 0x99, 0x86, 0x6a, 0x9b, 0x80, 0x9d, 0x9a, 0x3f, - 0x8a, 0x50, 0xb3, 0xa6, 0x69, 0x85, 0xa0, 0x07, 0xa4, 0x6c, 0xd2, 0xaa, 0x81, 0x40, 0xad, 0x75, - 0xca, 0x01, 0x21, 0x8d, 0x66, 0x77, 0xc7, 0xbb, 0x83, 0x77, 0x67, 0x56, 0x3b, 0xb3, 0x21, 0xe6, - 0x03, 0x20, 0x8e, 0xe5, 0x86, 0xc4, 0x85, 0x23, 0x47, 0x0e, 0xfd, 0x10, 0x3d, 0xa1, 0xaa, 0x27, - 0x94, 0x43, 0x80, 0xe4, 0xc0, 0xd7, 0x40, 0x3b, 0xb3, 0x5e, 0xc7, 0x55, 0x11, 0x08, 0xca, 0xc5, - 0x9a, 0xf9, 0xbd, 0x37, 0xef, 0xdf, 0xef, 0xbd, 0xb7, 0x06, 0x5d, 0x17, 0xbb, 0xe3, 0x88, 0xb3, - 0xde, 0x90, 0x32, 0x1c, 0x51, 0x39, 0xee, 0x1d, 0xdd, 0x2c, 0xcf, 0x56, 0x92, 0x72, 0xc9, 0xe1, - 0x95, 0x42, 0xc7, 0x2a, 0xf1, 0xa3, 0x9b, 0x6b, 0xd7, 0x3c, 0x2e, 0x62, 0x2e, 0x90, 0x52, 0xe9, - 0xe9, 0x8b, 0xd6, 0x5f, 0x5b, 0x0d, 0x78, 0xc0, 0x35, 0x9e, 0x9f, 0x0a, 0x74, 0x05, 0xc7, 0x94, - 0xf1, 0x9e, 0xfa, 0x2d, 0xa0, 0x4e, 0xc0, 0x79, 0x10, 0x91, 0x9e, 0xba, 0xb9, 0xd9, 0xb0, 0x27, - 0x69, 0x4c, 0x84, 0xc4, 0x71, 0xa2, 0x15, 0xba, 0x3f, 0x1b, 0x60, 0xf5, 0x53, 0x2e, 0x29, 0x0b, - 0xfa, 0xfc, 0x4b, 0x92, 0xee, 0x51, 0x21, 0x77, 0xb1, 0x17, 0x12, 0x78, 0x03, 0x40, 0xc9, 0x25, - 0x8e, 0xd0, 0x91, 0x92, 0xa2, 0x24, 0x17, 0x9b, 0xc6, 0x86, 0xb1, 0x59, 0x71, 0x2e, 0x2b, 0xc9, - 0x85, 0x67, 0xf0, 0x73, 0x00, 0x27, 0xa1, 0xe7, 0xf1, 0x1e, 0x51, 0x9f, 0xa4, 0xc2, 0x9c, 0xdf, - 0x58, 0xd8, 0x6c, 0x6c, 0x6f, 0x59, 0x2f, 0xc8, 0xce, 0xba, 0x57, 0x9c, 0xfb, 0x85, 0x76, 0xee, - 0x79, 0x9f, 0x0d, 0xb9, 0xb3, 0x32, 0x7c, 0x4e, 0x22, 0xe0, 0x1b, 0x60, 0x99, 0x65, 0x31, 0xc2, - 0x9e, 0xa4, 0x47, 0x04, 0x0d, 0x13, 0x61, 0x2e, 0x6c, 0x18, 0x9b, 0x2d, 0xa7, 0xc9, 0xb2, 0x78, - 0x47, 0x81, 0xf7, 0x12, 0x71, 0xa7, 0xf2, 0xcd, 0x0f, 0x9d, 0xb9, 0xee, 0xf7, 0x0b, 0xc0, 0xfc, - 0x2b, 0xdb, 0xf0, 0x01, 0x58, 0x72, 0xa5, 0x87, 0x92, 0x91, 0x4a, 0xa4, 0x69, 0xbf, 0x77, 0x72, - 0xda, 0xb9, 0x1d, 0x50, 0x19, 0x66, 0xae, 0xe5, 0xf1, 0xb8, 0x57, 0x04, 0x1a, 0x61, 0x57, 0x6c, - 0x51, 0x3e, 0xb9, 0xf6, 0xe4, 0x38, 0x21, 0xc2, 0xb2, 0xf7, 0xfb, 0xb7, 0x6e, 0xbf, 0xd3, 0xcf, - 0xdc, 0x8f, 0xc8, 0xd8, 0x59, 0x74, 0xa5, 0xd7, 0x1f, 0x41, 0x08, 0x2a, 0xd8, 0xf7, 0x53, 0x73, - 0x3e, 0x37, 0xe7, 0xa8, 0x33, 0xfc, 0x18, 0x00, 0x8f, 0xc7, 0x31, 0x15, 0x82, 0x72, 0xa6, 0x22, - 0xad, 0xdb, 0x5b, 0x27, 0xa7, 0x9d, 0x75, 0x4d, 0xa1, 0xf0, 0x47, 0x16, 0xe5, 0xbd, 0x18, 0xcb, - 0xd0, 0x3a, 0x20, 0x01, 0xf6, 0xc6, 0x7b, 0xc4, 0x7b, 0xf6, 0x78, 0x0b, 0x14, 0x0c, 0xef, 0x11, - 0xcf, 0xb9, 0x60, 0x00, 0x6e, 0x02, 0x5d, 0x6e, 0xe4, 0x72, 0xe6, 0x13, 0x1f, 0x09, 0x2c, 0xcd, - 0x8a, 0xa2, 0x61, 0x59, 0xe1, 0xb6, 0x82, 0x07, 0x58, 0xc2, 0x0f, 0x40, 0x2d, 0xcf, 0xce, 0x27, - 0x91, 0x30, 0x17, 0x55, 0xe9, 0x5f, 0x7f, 0x61, 0xe9, 0xed, 0xc3, 0xdd, 0x3d, 0x12, 0x95, 0x05, - 0xaf, 0xba, 0xd2, 0xdb, 0x23, 0x91, 0x80, 0x6f, 0x82, 0x65, 0x2a, 0x50, 0xd9, 0x21, 0xc4, 0x37, - 0x97, 0x36, 0x8c, 0xcd, 0x9a, 0xd3, 0xa2, 0xe2, 0x70, 0x0a, 0xc2, 0x75, 0x50, 0xa7, 0x02, 0x7d, - 0x81, 0x69, 0x44, 0x7c, 0xb3, 0xaa, 0x34, 0x6a, 0x54, 0x7c, 0xa8, 0xee, 0xf0, 0x35, 0x00, 0xa8, - 0x40, 0x22, 0xc2, 0x22, 0x24, 0xbe, 0x59, 0x53, 0xd2, 0x3a, 0x15, 0x03, 0x0d, 0x74, 0x7f, 0x37, - 0xc0, 0xf2, 0xac, 0xfb, 0x97, 0xcf, 0xc9, 0xfb, 0xa0, 0x21, 0x24, 0x1e, 0x91, 0x14, 0x95, 0xd4, - 0xd4, 0x6d, 0xf3, 0xd9, 0xe3, 0xad, 0xd5, 0xa2, 0xc2, 0x3b, 0xbe, 0x9f, 0x12, 0x21, 0x06, 0x32, - 0xa5, 0x2c, 0x70, 0x80, 0x56, 0xce, 0x41, 0xf8, 0x16, 0xb8, 0x94, 0xdf, 0xf2, 0x7e, 0x97, 0xc7, - 0x28, 0xc4, 0x22, 0xd4, 0xfc, 0x39, 0xad, 0x02, 0x3e, 0x3c, 0xbe, 0x8f, 0x45, 0x98, 0x97, 0x40, - 0x73, 0x32, 0x25, 0xa3, 0xa6, 0x80, 0x01, 0x96, 0x5d, 0x04, 0x9a, 0xfb, 0xcc, 0x27, 0xc7, 0xc4, - 0xb7, 0x23, 0xee, 0x8d, 0xe0, 0x55, 0xb0, 0x14, 0x12, 0x1a, 0x84, 0xb2, 0x98, 0x9e, 0xe2, 0x06, - 0xaf, 0x81, 0x1a, 0x4e, 0x12, 0xed, 0x45, 0xf7, 0x4f, 0x15, 0x27, 0x89, 0xb2, 0xff, 0x2a, 0xa8, - 0x6b, 0xc2, 0xbe, 0x22, 0xbe, 0x8a, 0xa0, 0xe6, 0x4c, 0x81, 0xee, 0xb7, 0x06, 0x68, 0xf5, 0x33, - 0xd7, 0xc1, 0xcc, 0xdf, 0xcd, 0xfb, 0x44, 0xc2, 0xeb, 0xa0, 0x29, 0x24, 0x4e, 0x25, 0x9a, 0x71, - 0xd4, 0x50, 0xd8, 0x7d, 0xed, 0x6d, 0x03, 0xe4, 0xd3, 0x82, 0x92, 0xcc, 0x45, 0x29, 0x66, 0xbe, - 0xf2, 0x58, 0x71, 0x00, 0xcb, 0xe2, 0xc2, 0x14, 0x6c, 0x17, 0x7d, 0x2b, 0x63, 0xc2, 0xa4, 0xf2, - 0xda, 0x74, 0x2e, 0x20, 0x79, 0xd2, 0x24, 0xe1, 0x5e, 0x88, 0x58, 0x16, 0x4f, 0x92, 0x56, 0xc0, - 0x27, 0x59, 0xdc, 0xfd, 0xba, 0x02, 0x6a, 0x77, 0xf3, 0x61, 0x63, 0x1e, 0x81, 0x87, 0xa0, 0x3e, - 0x4c, 0xd0, 0x4b, 0x62, 0xb5, 0x3a, 0x4c, 0x6c, 0xc5, 0xeb, 0x75, 0xd0, 0x74, 0xf3, 0x82, 0x4e, - 0x92, 0xd4, 0x19, 0x34, 0x14, 0x56, 0x24, 0xf9, 0x10, 0xd4, 0xca, 0x04, 0x55, 0x02, 0xf6, 0x9d, - 0x93, 0xd3, 0xce, 0xbb, 0xff, 0xd4, 0xef, 0xc0, 0x0b, 0x19, 0x4f, 0xd3, 0xa2, 0x20, 0x4e, 0x35, - 0x29, 0x2a, 0x73, 0x03, 0x40, 0x0f, 0x33, 0xce, 0xa8, 0x87, 0x23, 0x54, 0x72, 0x56, 0x51, 0x15, - 0xba, 0x5c, 0x4a, 0x76, 0x0a, 0xf2, 0xba, 0xa0, 0x35, 0xe4, 0xe9, 0x68, 0xaa, 0xb8, 0xa8, 0x14, - 0x1b, 0x39, 0x38, 0xd1, 0x49, 0xc0, 0xd5, 0xa9, 0xc5, 0x72, 0x73, 0x0a, 0x1a, 0xa8, 0x91, 0xfb, - 0x77, 0x61, 0xdf, 0x7d, 0x70, 0x38, 0x18, 0xd0, 0xc0, 0x59, 0x2d, 0x2d, 0x4f, 0xf6, 0xe0, 0x80, - 0x06, 0x70, 0x08, 0x56, 0x54, 0x54, 0x33, 0xce, 0xaa, 0xff, 0xd9, 0xd9, 0xa5, 0xdc, 0xe8, 0x05, - 0x3f, 0xdd, 0xef, 0xe6, 0xc1, 0xfa, 0xf3, 0xfb, 0x77, 0x40, 0x03, 0x46, 0x59, 0xa0, 0xc6, 0xfd, - 0x7f, 0xeb, 0x8d, 0x99, 0x01, 0xc8, 0x7b, 0x63, 0x61, 0x76, 0x00, 0xb6, 0xc1, 0x2b, 0xf9, 0x4a, - 0x25, 0x3e, 0x52, 0x1d, 0x23, 0x90, 0xc7, 0x33, 0x26, 0x49, 0xaa, 0x1a, 0x65, 0xc1, 0xb9, 0xa2, - 0x85, 0x6a, 0x64, 0xc5, 0xae, 0x16, 0xc1, 0x03, 0xd0, 0xd4, 0x7b, 0x0e, 0x65, 0x4c, 0xd2, 0x48, - 0x51, 0xde, 0xd8, 0x5e, 0xb3, 0xf4, 0x57, 0xd5, 0x9a, 0x7c, 0x55, 0xad, 0x72, 0x3d, 0xda, 0xad, - 0x27, 0xa7, 0x9d, 0xb9, 0x47, 0xbf, 0x76, 0x8c, 0x1f, 0xff, 0xf8, 0xe9, 0x6d, 0xc3, 0x69, 0xe8, - 0xe7, 0x0f, 0xf3, 0xd7, 0xf6, 0xc1, 0x93, 0xb3, 0xb6, 0xf1, 0xf4, 0xac, 0x6d, 0xfc, 0x76, 0xd6, - 0x36, 0x1e, 0x9d, 0xb7, 0xe7, 0x9e, 0x9e, 0xb7, 0xe7, 0x7e, 0x39, 0x6f, 0xcf, 0x7d, 0xb6, 0xfd, - 0xf7, 0xd9, 0x1f, 0x4f, 0xff, 0x3f, 0xa8, 0x42, 0xb8, 0x4b, 0xca, 0xfb, 0xad, 0x3f, 0x03, 0x00, - 0x00, 0xff, 0xff, 0xe6, 0xb7, 0x70, 0x5d, 0x60, 0x08, 0x00, 0x00, + // 916 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0x4f, 0x6f, 0xdc, 0x44, + 0x14, 0x5f, 0x27, 0x9b, 0xec, 0xee, 0xec, 0x6e, 0x68, 0xa6, 0xa1, 0x72, 0x13, 0xd8, 0xdd, 0xae, + 0x40, 0x5a, 0xa1, 0xc6, 0xa6, 0x69, 0x85, 0x50, 0x6f, 0x75, 0xd2, 0xaa, 0x81, 0x40, 0x57, 0xde, + 0x94, 0x03, 0x42, 0x1a, 0x8d, 0xed, 0xb1, 0x3d, 0xac, 0x3d, 0x63, 0x79, 0xc6, 0x4b, 0x97, 0x0f, + 0x80, 0x38, 0x96, 0x1b, 0x47, 0x8e, 0x1c, 0x39, 0xf0, 0x21, 0x7a, 0x42, 0x15, 0x27, 0x14, 0xa4, + 0x80, 0x92, 0x03, 0x5f, 0x03, 0x79, 0xec, 0xf5, 0x26, 0x11, 0x08, 0xc4, 0x9f, 0x8b, 0x35, 0xef, + 0xf7, 0x9e, 0xdf, 0x9f, 0xdf, 0x7b, 0xf3, 0x06, 0x0c, 0x1d, 0xec, 0xcc, 0x23, 0xce, 0x4c, 0x9f, + 0x32, 0x1c, 0x51, 0x39, 0x37, 0x67, 0x77, 0xaa, 0xb3, 0x91, 0xa4, 0x5c, 0x72, 0x78, 0xbd, 0xb4, + 0x31, 0x2a, 0x7c, 0x76, 0x67, 0xfb, 0xa6, 0xcb, 0x45, 0xcc, 0x05, 0x52, 0x26, 0x66, 0x21, 0x14, + 0xf6, 0xdb, 0x5b, 0x01, 0x0f, 0x78, 0x81, 0xe7, 0xa7, 0x12, 0xdd, 0xc4, 0x31, 0x65, 0xdc, 0x54, + 0xdf, 0x12, 0xea, 0x07, 0x9c, 0x07, 0x11, 0x31, 0x95, 0xe4, 0x64, 0xbe, 0x29, 0x69, 0x4c, 0x84, + 0xc4, 0x71, 0x52, 0x18, 0x0c, 0x7f, 0xd0, 0xc0, 0xd6, 0x47, 0x5c, 0x52, 0x16, 0x8c, 0xf9, 0x67, + 0x24, 0x3d, 0xa0, 0x42, 0xee, 0x63, 0x37, 0x24, 0xf0, 0x36, 0x80, 0x92, 0x4b, 0x1c, 0xa1, 0x99, + 0xd2, 0xa2, 0x24, 0x57, 0xeb, 0xda, 0x40, 0x1b, 0xd5, 0xed, 0x6b, 0x4a, 0x73, 0xe1, 0x37, 0xf8, + 0x09, 0x80, 0x8b, 0xd4, 0xf3, 0x7c, 0x67, 0xd4, 0x23, 0xa9, 0xd0, 0x57, 0x06, 0xab, 0xa3, 0xf6, + 0xde, 0xae, 0xf1, 0x07, 0xd5, 0x19, 0x8f, 0xca, 0xf3, 0xb8, 0xb4, 0xce, 0x23, 0x1f, 0x32, 0x9f, + 0xdb, 0x9b, 0xfe, 0x15, 0x8d, 0x80, 0x6f, 0x80, 0x0d, 0x96, 0xc5, 0x08, 0xbb, 0x92, 0xce, 0x08, + 0xf2, 0x13, 0xa1, 0xaf, 0x0e, 0xb4, 0x51, 0xd7, 0xee, 0xb0, 0x2c, 0x7e, 0xa0, 0xc0, 0x47, 0x89, + 0xb8, 0x5f, 0xff, 0xf2, 0x9b, 0x7e, 0x6d, 0xf8, 0xf3, 0x0a, 0xd0, 0xff, 0xcc, 0x37, 0x7c, 0x02, + 0xd6, 0x1d, 0xe9, 0xa2, 0x64, 0xaa, 0x0a, 0xe9, 0x58, 0xef, 0x9e, 0x9c, 0xf6, 0xef, 0x05, 0x54, + 0x86, 0x99, 0x63, 0xb8, 0x3c, 0x36, 0xcb, 0x44, 0x23, 0xec, 0x88, 0x5d, 0xca, 0x17, 0xa2, 0x29, + 0xe7, 0x09, 0x11, 0x86, 0x75, 0x38, 0xbe, 0x7b, 0xef, 0xed, 0x71, 0xe6, 0xbc, 0x4f, 0xe6, 0xf6, + 0x9a, 0x23, 0xdd, 0xf1, 0x14, 0x42, 0x50, 0xc7, 0x9e, 0x97, 0xea, 0x2b, 0xb9, 0x3b, 0x5b, 0x9d, + 0xe1, 0x07, 0x00, 0xb8, 0x3c, 0x8e, 0xa9, 0x10, 0x94, 0x33, 0x95, 0x69, 0xcb, 0xda, 0x3d, 0x39, + 0xed, 0xef, 0x14, 0x2d, 0x14, 0xde, 0xd4, 0xa0, 0xdc, 0x8c, 0xb1, 0x0c, 0x8d, 0x23, 0x12, 0x60, + 0x77, 0x7e, 0x40, 0xdc, 0x1f, 0xbf, 0xdf, 0x05, 0x65, 0x87, 0x0f, 0x88, 0x6b, 0x5f, 0x70, 0x00, + 0x47, 0xa0, 0xa0, 0x1b, 0x39, 0x9c, 0x79, 0xc4, 0x43, 0x02, 0x4b, 0xbd, 0xae, 0xda, 0xb0, 0xa1, + 0x70, 0x4b, 0xc1, 0x13, 0x2c, 0xe1, 0x9b, 0x60, 0x83, 0x0a, 0x54, 0x75, 0x98, 0x78, 0xfa, 0xda, + 0x40, 0x1b, 0x35, 0xed, 0x2e, 0x15, 0xc7, 0x4b, 0x10, 0xee, 0x80, 0x16, 0x15, 0xe8, 0x53, 0x4c, + 0x23, 0xe2, 0xe9, 0xeb, 0xca, 0xa2, 0x49, 0xc5, 0x7b, 0x4a, 0x86, 0xaf, 0x03, 0x40, 0x05, 0x12, + 0x11, 0x16, 0x21, 0xf1, 0xf4, 0x86, 0xd2, 0xb6, 0xa8, 0x98, 0x14, 0xc0, 0x10, 0x81, 0xce, 0x21, + 0xf3, 0xc8, 0x33, 0xe2, 0x59, 0x11, 0x77, 0xa7, 0xf0, 0x06, 0x58, 0x0f, 0x09, 0x0d, 0x42, 0x59, + 0x4e, 0x46, 0x29, 0xc1, 0x9b, 0xa0, 0x89, 0x93, 0x04, 0x85, 0x58, 0x84, 0x25, 0x37, 0x0d, 0x9c, + 0x24, 0x8f, 0xb1, 0x08, 0xe1, 0x6b, 0xa0, 0x55, 0x74, 0xf8, 0x73, 0xe2, 0x29, 0x76, 0x9a, 0xf6, + 0x12, 0x18, 0x7e, 0xa5, 0x81, 0xee, 0x38, 0x73, 0x6c, 0xcc, 0xbc, 0xfd, 0x9c, 0x03, 0x09, 0x6f, + 0x81, 0x8e, 0x90, 0x38, 0x95, 0xe8, 0x52, 0xa0, 0xb6, 0xc2, 0x1e, 0x17, 0xd1, 0x06, 0x20, 0x9f, + 0x04, 0x94, 0x64, 0x0e, 0x4a, 0x31, 0xf3, 0x54, 0xc4, 0xba, 0x0d, 0x58, 0x16, 0x97, 0xae, 0x60, + 0xaf, 0xec, 0x89, 0x8c, 0x09, 0x93, 0x2a, 0x6a, 0xc7, 0xbe, 0x80, 0xe4, 0x9c, 0x90, 0x84, 0xbb, + 0x21, 0x62, 0x59, 0x5c, 0xb2, 0xdb, 0x54, 0xc0, 0x87, 0x59, 0x3c, 0xfc, 0xa2, 0x0e, 0x9a, 0x0f, + 0xf3, 0x41, 0x62, 0x2e, 0x81, 0xc7, 0xa0, 0xe5, 0x27, 0xe8, 0x3f, 0x9a, 0xa2, 0x86, 0x9f, 0x58, + 0x6a, 0x8e, 0x6e, 0x81, 0x8e, 0x93, 0x13, 0xba, 0x28, 0xb2, 0xa8, 0xa0, 0xad, 0xb0, 0xb2, 0xc8, + 0xa7, 0xa0, 0x59, 0x15, 0xa8, 0x0a, 0xb0, 0xee, 0x9f, 0x9c, 0xf6, 0xdf, 0xf9, 0xbb, 0x71, 0x27, + 0x6e, 0xc8, 0x78, 0x9a, 0x96, 0x84, 0xd8, 0x8d, 0xa4, 0x64, 0xe6, 0x36, 0x80, 0x2e, 0x66, 0x9c, + 0x51, 0x17, 0x47, 0xa8, 0xea, 0x59, 0x5d, 0x31, 0x74, 0xad, 0xd2, 0x3c, 0x28, 0x9b, 0x37, 0x04, + 0x5d, 0x9f, 0xa7, 0xd3, 0xa5, 0xe1, 0x9a, 0x32, 0x6c, 0xe7, 0xe0, 0xc2, 0x26, 0x01, 0x37, 0x96, + 0x1e, 0xab, 0xad, 0x20, 0x68, 0xa0, 0x86, 0xed, 0x9f, 0xa5, 0xfd, 0xf0, 0xc9, 0xf1, 0x64, 0x42, + 0x03, 0x7b, 0xab, 0xf2, 0xbc, 0xb8, 0xe3, 0x13, 0x1a, 0x40, 0x1f, 0x6c, 0xaa, 0xac, 0x2e, 0x05, + 0x6b, 0xfc, 0xeb, 0x60, 0xaf, 0xe4, 0x4e, 0x2f, 0xc4, 0x19, 0x7e, 0xbd, 0x02, 0x76, 0xae, 0xee, + 0x96, 0x09, 0x0d, 0x18, 0x65, 0x81, 0x5a, 0x2f, 0xff, 0xdb, 0x6c, 0x5c, 0xba, 0x00, 0xf9, 0x6c, + 0xac, 0x5e, 0xbe, 0x00, 0x7b, 0xe0, 0xd5, 0x7c, 0x5d, 0x10, 0x0f, 0xa9, 0x89, 0x11, 0xc8, 0xe5, + 0x19, 0x93, 0x24, 0x55, 0x83, 0xb2, 0x6a, 0x5f, 0x2f, 0x94, 0xea, 0xca, 0x8a, 0xfd, 0x42, 0x05, + 0x8f, 0x40, 0xa7, 0xd8, 0x01, 0x28, 0x63, 0x92, 0x46, 0xaa, 0xe5, 0xed, 0xbd, 0x6d, 0xa3, 0x78, + 0x31, 0x8c, 0xc5, 0x8b, 0x61, 0x54, 0xab, 0xc3, 0xea, 0xbe, 0x38, 0xed, 0xd7, 0x9e, 0xff, 0xd2, + 0xd7, 0xbe, 0xfd, 0xed, 0xbb, 0xb7, 0x34, 0xbb, 0x5d, 0xfc, 0xfe, 0x34, 0xff, 0xdb, 0x3a, 0x7a, + 0x71, 0xd6, 0xd3, 0x5e, 0x9e, 0xf5, 0xb4, 0x5f, 0xcf, 0x7a, 0xda, 0xf3, 0xf3, 0x5e, 0xed, 0xe5, + 0x79, 0xaf, 0xf6, 0xd3, 0x79, 0xaf, 0xf6, 0xf1, 0xde, 0x5f, 0x57, 0xff, 0x6c, 0xf9, 0x36, 0x2a, + 0x22, 0x9c, 0x75, 0x15, 0xfd, 0xee, 0xef, 0x01, 0x00, 0x00, 0xff, 0xff, 0x04, 0xb9, 0x65, 0xd0, + 0x3c, 0x07, 0x00, 0x00, } func (m *VotingPowerDistCache) Marshal() (dAtA []byte, err error) { @@ -687,7 +604,7 @@ func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error dAtA[i] = 0 } i-- - dAtA[i] = 0x40 + dAtA[i] = 0x38 } if m.IsJailed { i-- @@ -697,7 +614,7 @@ func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error dAtA[i] = 0 } i-- - dAtA[i] = 0x38 + dAtA[i] = 0x30 } if m.IsTimestamped { i-- @@ -707,21 +624,7 @@ func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error dAtA[i] = 0 } i-- - dAtA[i] = 0x30 - } - if len(m.BtcDels) > 0 { - for iNdEx := len(m.BtcDels) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.BtcDels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintFinality(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } + dAtA[i] = 0x28 } if m.TotalBondedSat != 0 { i = encodeVarintFinality(dAtA, i, uint64(m.TotalBondedSat)) @@ -762,60 +665,6 @@ func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } -func (m *BTCDelDistInfo) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *BTCDelDistInfo) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *BTCDelDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.TotalSat != 0 { - i = encodeVarintFinality(dAtA, i, uint64(m.TotalSat)) - i-- - dAtA[i] = 0x20 - } - if len(m.StakingTxHash) > 0 { - i -= len(m.StakingTxHash) - copy(dAtA[i:], m.StakingTxHash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.StakingTxHash))) - i-- - dAtA[i] = 0x1a - } - if len(m.StakerAddr) > 0 { - i -= len(m.StakerAddr) - copy(dAtA[i:], m.StakerAddr) - i = encodeVarintFinality(dAtA, i, uint64(len(m.StakerAddr))) - i-- - dAtA[i] = 0x12 - } - if m.BtcPk != nil { - { - size := m.BtcPk.Size() - i -= size - if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintFinality(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1102,12 +951,6 @@ func (m *FinalityProviderDistInfo) Size() (n int) { if m.TotalBondedSat != 0 { n += 1 + sovFinality(uint64(m.TotalBondedSat)) } - if len(m.BtcDels) > 0 { - for _, e := range m.BtcDels { - l = e.Size() - n += 1 + l + sovFinality(uint64(l)) - } - } if m.IsTimestamped { n += 2 } @@ -1120,30 +963,6 @@ func (m *FinalityProviderDistInfo) Size() (n int) { return n } -func (m *BTCDelDistInfo) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.BtcPk != nil { - l = m.BtcPk.Size() - n += 1 + l + sovFinality(uint64(l)) - } - l = len(m.StakerAddr) - if l > 0 { - n += 1 + l + sovFinality(uint64(l)) - } - l = len(m.StakingTxHash) - if l > 0 { - n += 1 + l + sovFinality(uint64(l)) - } - if m.TotalSat != 0 { - n += 1 + sovFinality(uint64(m.TotalSat)) - } - return n -} - func (m *IndexedBlock) Size() (n int) { if m == nil { return 0 @@ -1524,40 +1343,6 @@ func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { } } case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcDels", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.BtcDels = append(m.BtcDels, &BTCDelDistInfo{}) - if err := m.BtcDels[len(m.BtcDels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field IsTimestamped", wireType) } @@ -1577,7 +1362,7 @@ func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { } } m.IsTimestamped = bool(v != 0) - case 7: + case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field IsJailed", wireType) } @@ -1597,7 +1382,7 @@ func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { } } m.IsJailed = bool(v != 0) - case 8: + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field IsSlashed", wireType) } @@ -1638,174 +1423,6 @@ func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { } return nil } -func (m *BTCDelDistInfo) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BTCDelDistInfo: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BTCDelDistInfo: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonlabs_io_babylon_types.BIP340PubKey - m.BtcPk = &v - if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakerAddr", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakerAddr = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakingTxHash = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TotalSat", wireType) - } - m.TotalSat = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.TotalSat |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipFinality(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthFinality - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *IndexedBlock) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index 30bd8b13f..9db21938c 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -279,6 +279,48 @@ func (m *MockIncentiveKeeper) EXPECT() *MockIncentiveKeeperMockRecorder { return m.recorder } +// BtcDelegationActivated mocks base method. +func (m *MockIncentiveKeeper) BtcDelegationActivated(ctx context.Context, fp, del types1.AccAddress, sat uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BtcDelegationActivated", ctx, fp, del, sat) + ret0, _ := ret[0].(error) + return ret0 +} + +// BtcDelegationActivated indicates an expected call of BtcDelegationActivated. +func (mr *MockIncentiveKeeperMockRecorder) BtcDelegationActivated(ctx, fp, del, sat interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BtcDelegationActivated", reflect.TypeOf((*MockIncentiveKeeper)(nil).BtcDelegationActivated), ctx, fp, del, sat) +} + +// BtcDelegationUnbonded mocks base method. +func (m *MockIncentiveKeeper) BtcDelegationUnbonded(ctx context.Context, fp, del types1.AccAddress, sat uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BtcDelegationUnbonded", ctx, fp, del, sat) + ret0, _ := ret[0].(error) + return ret0 +} + +// BtcDelegationUnbonded indicates an expected call of BtcDelegationUnbonded. +func (mr *MockIncentiveKeeperMockRecorder) BtcDelegationUnbonded(ctx, fp, del, sat interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BtcDelegationUnbonded", reflect.TypeOf((*MockIncentiveKeeper)(nil).BtcDelegationUnbonded), ctx, fp, del, sat) +} + +// FpSlashed mocks base method. +func (m *MockIncentiveKeeper) FpSlashed(ctx context.Context, fp types1.AccAddress) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FpSlashed", ctx, fp) + ret0, _ := ret[0].(error) + return ret0 +} + +// FpSlashed indicates an expected call of FpSlashed. +func (mr *MockIncentiveKeeperMockRecorder) FpSlashed(ctx, fp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FpSlashed", reflect.TypeOf((*MockIncentiveKeeper)(nil).FpSlashed), ctx, fp) +} + // IndexRefundableMsg mocks base method. func (m *MockIncentiveKeeper) IndexRefundableMsg(ctx context.Context, msg types1.Msg) { m.ctrl.T.Helper() diff --git a/x/finality/types/power_table.go b/x/finality/types/power_table.go index c25aebe72..c844d358d 100644 --- a/x/finality/types/power_table.go +++ b/x/finality/types/power_table.go @@ -146,7 +146,6 @@ func NewFinalityProviderDistInfo(fp *bstypes.FinalityProvider) *FinalityProvider Addr: sdk.MustAccAddressFromBech32(fp.Addr), Commission: fp.Commission, TotalBondedSat: 0, - BtcDels: []*BTCDelDistInfo{}, } } @@ -154,30 +153,18 @@ func (v *FinalityProviderDistInfo) GetAddress() sdk.AccAddress { return v.Addr } -func (v *FinalityProviderDistInfo) AddBTCDel(btcDel *bstypes.BTCDelegation) { - btcDelDistInfo := &BTCDelDistInfo{ - BtcPk: btcDel.BtcPk, - StakerAddr: btcDel.StakerAddr, - StakingTxHash: btcDel.MustGetStakingTxHash().String(), - TotalSat: btcDel.TotalSat, - } - v.BtcDels = append(v.BtcDels, btcDelDistInfo) - v.TotalBondedSat += btcDelDistInfo.TotalSat +func (v *FinalityProviderDistInfo) AddBondedSats(sats uint64) { + v.TotalBondedSat += sats } -func (v *FinalityProviderDistInfo) AddBTCDelDistInfo(d *BTCDelDistInfo) { - v.BtcDels = append(v.BtcDels, d) - v.TotalBondedSat += d.TotalSat +func (v *FinalityProviderDistInfo) RemoveBondedSats(sats uint64) { + v.TotalBondedSat -= sats } // GetBTCDelPortion returns the portion of a BTC delegation's voting power out of // the finality provider's total voting power -func (v *FinalityProviderDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdkmath.LegacyDec { - return sdkmath.LegacyNewDec(int64(d.TotalSat)).QuoTruncate(sdkmath.LegacyNewDec(int64(v.TotalBondedSat))) -} - -func (d *BTCDelDistInfo) GetAddress() sdk.AccAddress { - return sdk.MustAccAddressFromBech32(d.StakerAddr) +func (v *FinalityProviderDistInfo) GetBTCDelPortion(totalSatDelegation uint64) sdkmath.LegacyDec { + return sdkmath.LegacyNewDec(int64(totalSatDelegation)).QuoTruncate(sdkmath.LegacyNewDec(int64(v.TotalBondedSat))) } // SortFinalityProvidersWithZeroedVotingPower sorts the finality providers slice, diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go index a078143ca..79e989bee 100644 --- a/x/incentive/genesis.go +++ b/x/incentive/genesis.go @@ -2,6 +2,7 @@ package incentive import ( "context" + "github.com/babylonlabs-io/babylon/x/incentive/keeper" "github.com/babylonlabs-io/babylon/x/incentive/types" ) @@ -11,6 +12,7 @@ func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisSta if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) } + // TODO(rafilx): add gauge, reward tracker } // ExportGenesis returns the module's exported genesis @@ -18,5 +20,6 @@ func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) + // TODO(rafilx): add gauge, reward tracker return genesis } diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index a8ad38f33..f1daebaad 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -19,6 +19,7 @@ func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, dc *ftypes. // failing to get a reward gauge at previous height is a programming error panic("failed to get a reward gauge at previous height") } + // reward each of the finality provider and its BTC delegations in proportion for i, fp := range dc.FinalityProviders { // only reward the first NumActiveFps finality providers @@ -34,14 +35,14 @@ func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, dc *ftypes. // reward the finality provider with commission coinsForCommission := types.GetCoinsPortion(coinsForFpsAndDels, *fp.Commission) k.accumulateRewardGauge(ctx, types.FinalityProviderType, fp.GetAddress(), coinsForCommission) + // reward the rest of coins to each BTC delegation proportional to its voting power portion coinsForBTCDels := coinsForFpsAndDels.Sub(coinsForCommission...) - for _, btcDel := range fp.BtcDels { - btcDelPortion := fp.GetBTCDelPortion(btcDel) - coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) - k.accumulateRewardGauge(ctx, types.BTCDelegationType, btcDel.GetAddress(), coinsForDel) + if err := k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp.GetAddress(), coinsForBTCDels); err != nil { + panic(err) } } + // TODO: prune unnecessary state (delete BTCStakingGauge after the amount is used) } func (k Keeper) accumulateBTCStakingReward(ctx context.Context, btcStakingReward sdk.Coins) { diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go index 957886b93..526e02971 100644 --- a/x/incentive/keeper/btc_staking_gauge_test.go +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/babylonlabs-io/babylon/testutil/coins" "github.com/babylonlabs-io/babylon/testutil/datagen" testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/x/incentive/types" @@ -15,25 +16,24 @@ import ( func FuzzRewardBTCStaking(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() r := rand.New(rand.NewSource(seed)) - ctrl := gomock.NewController(t) defer ctrl.Finish() // mock bank keeper bankKeeper := types.NewMockBankKeeper(ctrl) - // create incentive keeper - keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, nil, nil) + k, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, nil, nil) height := datagen.RandomInt(r, 1000) ctx = datagen.WithCtxHeight(ctx, height) // set a random gauge gauge := datagen.GenRandomGauge(r) - keeper.SetBTCStakingGauge(ctx, height, gauge) + k.SetBTCStakingGauge(ctx, height, gauge) // generate a random voting power distribution cache - dc, err := datagen.GenRandomVotingPowerDistCache(r, 100) + dc, btcTotalSatByDelAddressByFpAddress, err := datagen.GenRandomVotingPowerDistCache(r, 100) require.NoError(t, err) // expected values @@ -41,6 +41,7 @@ func FuzzRewardBTCStaking(f *testing.F) { fpRewardMap := map[string]sdk.Coins{} // key: address, value: reward btcDelRewardMap := map[string]sdk.Coins{} // key: address, value: reward + sumCoinsForDels := sdk.NewCoins() for _, fp := range dc.FinalityProviders { fpPortion := dc.GetFinalityProviderPortion(fp) coinsForFpsAndDels := gauge.GetCoinsPortion(fpPortion) @@ -49,36 +50,76 @@ func FuzzRewardBTCStaking(f *testing.F) { fpRewardMap[fp.GetAddress().String()] = coinsForCommission distributedCoins.Add(coinsForCommission...) } + coinsForBTCDels := coinsForFpsAndDels.Sub(coinsForCommission...) - for _, btcDel := range fp.BtcDels { - btcDelPortion := fp.GetBTCDelPortion(btcDel) + sumCoinsForDels = sumCoinsForDels.Add(coinsForBTCDels...) + fpAddr := fp.GetAddress() + + for delAddrStr, delSat := range btcTotalSatByDelAddressByFpAddress[fpAddr.String()] { + btcDelAddr := sdk.MustAccAddressFromBech32(delAddrStr) + err := k.BtcDelegationActivated(ctx, fpAddr, btcDelAddr, delSat) + require.NoError(t, err) + + btcDelPortion := fp.GetBTCDelPortion(delSat) coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) if coinsForDel.IsAllPositive() { - btcDelRewardMap[btcDel.GetAddress().String()] = coinsForDel + btcDelRewardMap[delAddrStr] = coinsForDel distributedCoins.Add(coinsForDel...) } } } // distribute rewards in the gauge to finality providers/delegations - keeper.RewardBTCStaking(ctx, height, dc) + k.RewardBTCStaking(ctx, height, dc) + + for _, fp := range dc.FinalityProviders { + fpAddr := fp.GetAddress() + for delAddrStr, delSat := range btcTotalSatByDelAddressByFpAddress[fpAddr.String()] { + delAddr := sdk.MustAccAddressFromBech32(delAddrStr) + delRwd, err := k.GetBTCDelegationRewardsTracker(ctx, fpAddr, delAddr) + require.NoError(t, err) + require.Equal(t, delRwd.TotalActiveSat.Uint64(), delSat) + + // makes sure the rewards added reach the delegation gauge + err = k.BtcDelegationActivated(ctx, fpAddr, delAddr, 0) + require.NoError(t, err) + } + fpCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fpAddr) + require.NoError(t, err) + require.Equal(t, fpCurrentRwd.TotalActiveSat.Uint64(), fp.TotalBondedSat) + } // assert consistency between reward map and reward gauge for addrStr, reward := range fpRewardMap { addr, err := sdk.AccAddressFromBech32(addrStr) require.NoError(t, err) - rg := keeper.GetRewardGauge(ctx, types.FinalityProviderType, addr) + rg := k.GetRewardGauge(ctx, types.FinalityProviderType, addr) require.NotNil(t, rg) require.Equal(t, reward, rg.Coins) } + + sumRewards := sdk.NewCoins() for addrStr, reward := range btcDelRewardMap { addr, err := sdk.AccAddressFromBech32(addrStr) require.NoError(t, err) - rg := keeper.GetRewardGauge(ctx, types.BTCDelegationType, addr) + rg := k.GetRewardGauge(ctx, types.BTCDelegationType, addr) require.NotNil(t, rg) - require.Equal(t, reward, rg.Coins) + + // A little bit of rewards could be lost in the process due to precision points + // so 0.1% difference can be considered okay + require.Truef(t, coins.CoinsDiffInPointOnePercentMargin(reward, rg.Coins), + "BTC delegation failed within the margin of error 0.1%\nRewards: %s\nGauge: %s", + reward.String(), rg.Coins.String(), + ) + + sumRewards = sumRewards.Add(reward...) } + require.Truef(t, coins.CoinsDiffInPointOnePercentMargin(sumCoinsForDels, sumRewards), + "Sum of total rewards failed within the margin of error 0.1%\nRewards: %s\nGauge: %s", + sumCoinsForDels.String(), sumRewards.String(), + ) + // assert distributedCoins is a subset of coins in gauge require.True(t, gauge.Coins.IsAllGTE(distributedCoins)) }) diff --git a/x/incentive/keeper/grpc_query.go b/x/incentive/keeper/grpc_query.go index a526fdc47..5ceb6b7e7 100644 --- a/x/incentive/keeper/grpc_query.go +++ b/x/incentive/keeper/grpc_query.go @@ -27,6 +27,10 @@ func (k Keeper) RewardGauges(goCtx context.Context, req *types.QueryRewardGauges // find reward gauge for _, sType := range types.GetAllStakeholderTypes() { + if err := k.sendAllBtcDelegationTypeToRewardsGauge(ctx, sType, address); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + rg := k.GetRewardGauge(ctx, sType, address) if rg == nil { continue diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index c66d3804f..7e93ba674 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -29,6 +29,15 @@ type ( authority string // name of the FeeCollector ModuleAccount feeCollectorName string + + // Collections structures for rewards + + // BTCDelegationRewardsTracker maps (FpAddr, DelAddr) => BTCDelegationRewardsTracker + BTCDelegationRewardsTracker collections.Map[collections.Pair[[]byte, []byte], types.BTCDelegationRewardsTracker] + // FinalityProviderHistoricalRewards maps (FpAddr, period) => FinalityProviderHistoricalRewards + FinalityProviderHistoricalRewards collections.Map[collections.Pair[[]byte, uint64], types.FinalityProviderHistoricalRewards] + // FinalityProviderCurrentRewards maps (FpAddr) => FinalityProviderCurrentRewards + FinalityProviderCurrentRewards collections.Map[[]byte, types.FinalityProviderCurrentRewards] } ) @@ -55,6 +64,32 @@ func NewKeeper( "refundable_msg_key_set", collections.BytesKey, ), + + // Collections structures for rewards + BTCDelegationRewardsTracker: collections.NewMap( + sb, + types.BTCDelegationRewardsTrackerKeyPrefix, + "btc_delegation_rewards_tracker", + // keys: (FpAddr, DelAddr) + collections.PairKeyCodec(collections.BytesKey, collections.BytesKey), + codec.CollValue[types.BTCDelegationRewardsTracker](cdc), + ), + FinalityProviderHistoricalRewards: collections.NewMap( + sb, + types.FinalityProviderHistoricalRewardsKeyPrefix, + "fp_historical_rewards", + // keys: (FpAddr, period) + collections.PairKeyCodec(collections.BytesKey, collections.Uint64Key), + codec.CollValue[types.FinalityProviderHistoricalRewards](cdc), + ), + FinalityProviderCurrentRewards: collections.NewMap( + sb, + types.FinalityProviderCurrentRewardsKeyPrefix, + "fp_current_rewards", + // key: (FpAddr) + collections.BytesKey, + codec.CollValue[types.FinalityProviderCurrentRewards](cdc), + ), authority: authority, feeCollectorName: feeCollectorName, } diff --git a/x/incentive/keeper/msg_server.go b/x/incentive/keeper/msg_server.go index 150e580c7..97bce041c 100644 --- a/x/incentive/keeper/msg_server.go +++ b/x/incentive/keeper/msg_server.go @@ -2,6 +2,7 @@ package keeper import ( "context" + errorsmod "cosmossdk.io/errors" "github.com/babylonlabs-io/babylon/x/incentive/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -53,6 +54,10 @@ func (ms msgServer) WithdrawReward(goCtx context.Context, req *types.MsgWithdraw return nil, status.Error(codes.InvalidArgument, err.Error()) } + if err := ms.sendAllBtcDelegationTypeToRewardsGauge(ctx, sType, addr); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + // withdraw reward, i.e., send withdrawable reward to the stakeholder address and clear the reward gauge withdrawnCoins, err := ms.withdrawReward(ctx, sType, addr) if err != nil { diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go index d05a66e0b..719e492a2 100644 --- a/x/incentive/keeper/reward_gauge.go +++ b/x/incentive/keeper/reward_gauge.go @@ -9,6 +9,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func (k Keeper) sendAllBtcDelegationTypeToRewardsGauge(ctx context.Context, sType types.StakeholderType, del sdk.AccAddress) error { + if sType != types.BTCDelegationType { + return nil + } + return k.sendAllBtcRewardsToGauge(ctx, del) +} + func (k Keeper) withdrawReward(ctx context.Context, sType types.StakeholderType, addr sdk.AccAddress) (sdk.Coins, error) { // retrieve reward gauge of the given stakeholder rg := k.GetRewardGauge(ctx, sType, addr) diff --git a/x/incentive/keeper/reward_tracker.go b/x/incentive/keeper/reward_tracker.go new file mode 100644 index 000000000..afee4994b --- /dev/null +++ b/x/incentive/keeper/reward_tracker.go @@ -0,0 +1,299 @@ +package keeper + +import ( + "context" + "errors" + + "github.com/babylonlabs-io/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + sdkmath "cosmossdk.io/math" +) + +// AddFinalityProviderRewardsForBtcDelegations gets the current finality provider rewards +// and adds rewards to it, without increasing the finality provider period +// it also does not initiliaze the FP, so it must have been initialized prior +// to adding rewards. In the sense that a FP would first receive active delegations sats +// be properly initialized (creates current and historical reward structures in the store) +// than will start to receive rewards for contributing. +func (k Keeper) AddFinalityProviderRewardsForBtcDelegations(ctx context.Context, fp sdk.AccAddress, rwd sdk.Coins) error { + fpCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + if err != nil { + return err + } + + fpCurrentRwd.AddRewards(rwd) + return k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) +} + +// BtcDelegationActivated adds new amount of active satoshi to the delegation +// and finality provider. Since it modifies the amount staked, it triggers +// the creation of a new period, which initializes the FP, creates +// historical reward tracker, withdraw the BTC delegation rewards to gauge +// and initializes a new delegation with the just ended period. +func (k Keeper) BtcDelegationActivated(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error { + amtSat := sdkmath.NewIntFromUint64(sat) + return k.btcDelegationModifiedWithPreInitDel(ctx, fp, del, func(ctx context.Context, fp, del sdk.AccAddress) error { + return k.addDelegationSat(ctx, fp, del, amtSat) + }) +} + +// BtcDelegationUnbonded it modifies the total amount of satoshi staked +// for the delegation (fp, del) and for the finality provider by subtracting. +// Since it modifies the active amount it triggers the increment of fp period, +// creationg of new historical reward, withdraw of rewards to gauge +// and initialization of a new delegation. +// It errors out if the unbond amount is higher than the total amount staked. +func (k Keeper) BtcDelegationUnbonded(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error { + amtSat := sdkmath.NewIntFromUint64(sat) + return k.btcDelegationModifiedWithPreInitDel(ctx, fp, del, func(ctx context.Context, fp, del sdk.AccAddress) error { + return k.subDelegationSat(ctx, fp, del, amtSat) + }) +} + +// FpSlashed a slashed finality provider should withdraw all the rewards +// available to it, by iterating over all the delegations for this FP +// and sending to the gauge. After the rewards are removed, it should +// delete every rewards tracker value in the store related to this slashed +// finality provider. +func (k Keeper) FpSlashed(ctx context.Context, fp sdk.AccAddress) error { + // finalize the period to get a new history with the current rewards available + endedPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fp) + if err != nil { + return err + } + + // remove all the rewards available from the just ended period + keysBtcDelRwdTracker := make([][]byte, 0) + if err := k.IterateBTCDelegationRewardsTracker(ctx, fp, func(fp, del sdk.AccAddress) error { + keysBtcDelRwdTracker = append(keysBtcDelRwdTracker, del.Bytes()) + return k.CalculateBTCDelegationRewardsAndSendToGauge(ctx, fp, del, endedPeriod) + }); err != nil { + return err + } + + // delete all reward tracker that correlates with the slashed finality provider. + k.deleteKeysFromBTCDelegationRewardsTracker(ctx, fp, keysBtcDelRwdTracker) + k.deleteAllFromFinalityProviderRwd(ctx, fp) + return nil +} + +// sendAllBtcRewardsToGauge iterates over all the finality providers associated +// with the delegator and withdraw the rewards available to the gauge. +// This creates new periods for each delegation and finality provider. +func (k Keeper) sendAllBtcRewardsToGauge(ctx context.Context, del sdk.AccAddress) error { + return k.iterBtcDelegationsByDelegator(ctx, del, func(del, fp sdk.AccAddress) error { + return k.btcDelegationModified(ctx, fp, del) + }) +} + +// btcDelegationModified just calls the BTC delegation modified without +// any action to modify the delegation prior to initialization. +// this could also be called SendBtcDelegationRewardsToGauge. +func (k Keeper) btcDelegationModified( + ctx context.Context, + fp, del sdk.AccAddress, +) error { + return k.btcDelegationModifiedWithPreInitDel(ctx, fp, del, func(ctx context.Context, fp, del sdk.AccAddress) error { return nil }) +} + +// btcDelegationModifiedWithPreInitDel does the procedure when a BTC delegation has +// some modification in its total amount of active satoshi staked. This function +// increments the finality provider period (that creates a new historical) with +// the ended period, calculates the delegation reward and send to the gauge +// and calls a function prior (preInitializeDelegation) to initialize a new +// BTC delegation, which is useful to apply subtract or add the total +// amount staked by the delegation and FP. +func (k Keeper) btcDelegationModifiedWithPreInitDel( + ctx context.Context, + fp, del sdk.AccAddress, + preInitializeDelegation func(ctx context.Context, fp, del sdk.AccAddress) error, +) error { + endedPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fp) + if err != nil { + return err + } + + if err := k.CalculateBTCDelegationRewardsAndSendToGauge(ctx, fp, del, endedPeriod); err != nil { + return err + } + + if err := preInitializeDelegation(ctx, fp, del); err != nil { + return err + } + + return k.initializeBTCDelegation(ctx, fp, del) +} + +// CalculateBTCDelegationRewardsAndSendToGauge calculates the rewards of the delegator based on the +// StartPeriodCumulativeReward and the received endPeriod and sends to the delegator gauge. +func (k Keeper) CalculateBTCDelegationRewardsAndSendToGauge(ctx context.Context, fp, del sdk.AccAddress, endPeriod uint64) error { + rewards, err := k.CalculateBTCDelegationRewards(ctx, fp, del, endPeriod) + if err != nil { + if !errors.Is(err, types.ErrBTCDelegationRewardsTrackerNotFound) { + return err + } + rewards = sdk.NewCoins() + } + + if rewards.IsZero() { + return nil + } + + k.accumulateRewardGauge(ctx, types.BTCDelegationType, del, rewards) + return nil +} + +// CalculateBTCDelegationRewards calculates the rewards entitled for this delegation +// from the starting period cumulative reward and the ending period received as parameter +// It returns the amount of rewards without decimals (it removes the DecimalAccumulatedRewards). +func (k Keeper) CalculateBTCDelegationRewards(ctx context.Context, fp, del sdk.AccAddress, endPeriod uint64) (sdk.Coins, error) { + btcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + if err != nil { + return sdk.Coins{}, err + } + + if btcDelRwdTracker.TotalActiveSat.IsZero() { + return sdk.NewCoins(), nil + } + + return k.calculateDelegationRewardsBetween(ctx, fp, btcDelRwdTracker, endPeriod) +} + +// calculateDelegationRewardsBetween calculate the rewards accured of a delegation between +// two period, the endingPeriod received in param and the StartPeriodCumulativeReward of +// the BTCDelegationRewardsTracker. It gets the CumulativeRewardsPerSat of the ending +// period and subtracts the CumulativeRewardsPerSat of the starting period +// that give the total amount of rewards that one satoshi is entitle to receive +// in rewards between those two period. To get the amount this delegation should +// receive, it multiplies by the total amount of active satoshi this delegation has. +// One note, before give out the rewards it quotes by the DecimalAccumulatedRewards +// to get it ready to be sent out to the delegator reward gauge. +func (k Keeper) calculateDelegationRewardsBetween( + ctx context.Context, + fp sdk.AccAddress, + btcDelRwdTracker types.BTCDelegationRewardsTracker, + endingPeriod uint64, +) (sdk.Coins, error) { + if btcDelRwdTracker.StartPeriodCumulativeReward > endingPeriod { + panic("startingPeriod cannot be greater than endingPeriod") + } + + // return staking * (ending - starting) + starting, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, btcDelRwdTracker.StartPeriodCumulativeReward) + if err != nil { + return sdk.Coins{}, err + } + + ending, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, endingPeriod) + if err != nil { + return sdk.Coins{}, err + } + + // creates the differenceWithDecimals amount of rewards (ending - starting) periods + // this differenceWithDecimals is the amount of rewards entitled per satoshi active stake + differenceWithDecimals := ending.CumulativeRewardsPerSat.Sub(starting.CumulativeRewardsPerSat...) + if differenceWithDecimals.IsAnyNegative() { + panic("negative rewards should not be possible") + } + + rewardsWithDecimals := differenceWithDecimals.MulInt(btcDelRwdTracker.TotalActiveSat) + // note: necessary to truncate so we don't allow withdrawing more rewardsWithDecimals than owed + // QuoInt already truncates + rewards := rewardsWithDecimals.QuoInt(types.DecimalAccumulatedRewards) + return rewards, nil +} + +// IncrementFinalityProviderPeriod gets or initializes the finality provider, +// increases the period from the current FP rewards and empty the rewards. +// It also creates a new historical with the ended period and sets the rewards +// of the newly historical period as the amount from the previous historical +// plus the amount of rewards that each satoshi staked is entitled to receive. +// The rewards in the historical are stored with multiplied decimals +// (DecimalAccumulatedRewards) to increase precision, and need to be +// reduced when the rewards are calculated in calculateDelegationRewardsBetween +// prior to send out to the delegator gauge. +func (k Keeper) IncrementFinalityProviderPeriod(ctx context.Context, fp sdk.AccAddress) (endedPeriod uint64, err error) { + // IncrementValidatorPeriod + // gets the current rewards and send to historical the current period (the rewards are stored as "shares" which means the amount of rewards per satoshi) + // sets new empty current rewards with new period + fpCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + if err != nil { + if !errors.Is(err, types.ErrFPCurrentRewardsNotFound) { + return 0, err + } + + // initialize Validator and return 1 as ended period due + // to the created historical FP rewards starts at period 0 + if _, err := k.initializeFinalityProvider(ctx, fp); err != nil { + return 0, err + } + return 1, nil + } + + currentRewardsPerSat := sdk.NewCoins() + if !fpCurrentRwd.TotalActiveSat.IsZero() { + currentRewardsPerSatWithDecimals := fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards) + currentRewardsPerSat = currentRewardsPerSatWithDecimals.QuoInt(fpCurrentRwd.TotalActiveSat) + } + + fpHistoricalRwd, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, fpCurrentRwd.Period-1) + if err != nil { + return 0, err + } + + newFpHistoricalRwd := types.NewFinalityProviderHistoricalRewards(fpHistoricalRwd.CumulativeRewardsPerSat.Add(currentRewardsPerSat...)) + if err := k.setFinalityProviderHistoricalRewards(ctx, fp, fpCurrentRwd.Period, newFpHistoricalRwd); err != nil { + return 0, err + } + + // initiates a new period with empty rewards and the same amount of active sat + newCurrentRwd := types.NewFinalityProviderCurrentRewards(sdk.NewCoins(), fpCurrentRwd.Period+1, fpCurrentRwd.TotalActiveSat) + if err := k.setFinalityProviderCurrentRewards(ctx, fp, newCurrentRwd); err != nil { + return 0, err + } + + return fpCurrentRwd.Period, nil +} + +// initializeFinalityProvider initializes a new finality provider current rewards at period 1, empty rewards and zero sats +// and also creates a new historical rewards at period 0 and zero rewards as well. +// It does not verifies if it exists prior to overwrite, who calls it needs to verify. +func (k Keeper) initializeFinalityProvider(ctx context.Context, fp sdk.AccAddress) (types.FinalityProviderCurrentRewards, error) { + // historical rewards starts at the period 0 + err := k.setFinalityProviderHistoricalRewards(ctx, fp, 0, types.NewFinalityProviderHistoricalRewards(sdk.NewCoins())) + if err != nil { + return types.FinalityProviderCurrentRewards{}, err + } + + // set current rewards (starting at period 1) + newFp := types.NewFinalityProviderCurrentRewards(sdk.NewCoins(), 1, sdkmath.ZeroInt()) + return newFp, k.setFinalityProviderCurrentRewards(ctx, fp, newFp) +} + +// initializeBTCDelegation creates a new BTCDelegationRewardsTracker from the +// previous acumulative rewards period of the finality provider. This function +// should be called right after a BTC delegator withdraw his rewards (in our +// case send the rewards to the reward gauge). Reminder that at every new +// modification to the amount of satoshi staked from this btc delegator to +// this finality provider (activivation or unbonding) of BTC delegations, it +// should withdraw all rewards (send to gauge) and initialize a new BTCDelegationRewardsTracker. +// TODO: add reference count to keep track of possible prunning state of val rewards +func (k Keeper) initializeBTCDelegation(ctx context.Context, fp, del sdk.AccAddress) error { + // period has already been incremented prior to call this function + // it is needed to store the period ended by this delegation action + // as a starting point of the delegation rewards calculation + valCurrentRewards, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + if err != nil { + return err + } + previousPeriod := valCurrentRewards.Period - 1 + + btcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + if err != nil { + return err + } + + rwd := types.NewBTCDelegationRewardsTracker(previousPeriod, btcDelRwdTracker.TotalActiveSat) + return k.setBTCDelegationRewardsTracker(ctx, fp, del, rwd) +} diff --git a/x/incentive/keeper/reward_tracker_store.go b/x/incentive/keeper/reward_tracker_store.go new file mode 100644 index 000000000..39768b941 --- /dev/null +++ b/x/incentive/keeper/reward_tracker_store.go @@ -0,0 +1,228 @@ +package keeper + +import ( + "context" + "errors" + + "cosmossdk.io/collections" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/prefix" + "github.com/babylonlabs-io/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// storeBTCDelegatorToFp returns the KVStore of the mapping del => fp +// note: it stores the finality provider as key and sets a one byte as value +// so each BTC delegator address can have multiple finality providers. +// Useful to iterate over all the pairs (fp,del) by filtering the +// delegator address. +// prefix: BTCDelegatorToFPKey +// key: (DelAddr, FpAddr) +// value: 0x00 +func (k Keeper) storeBTCDelegatorToFp(ctx context.Context, del sdk.AccAddress) prefix.Store { + storeAdaptor := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + st := prefix.NewStore(storeAdaptor, types.BTCDelegatorToFPKey) + return prefix.NewStore(st, del.Bytes()) +} + +// setBTCDelegatorToFP sets a new delegator to finality provider record. +func (k Keeper) setBTCDelegatorToFP(ctx context.Context, del, fp sdk.AccAddress) { + st := k.storeBTCDelegatorToFp(ctx, del) + st.Set(fp.Bytes(), []byte{0x00}) +} + +// iterBtcDelegationsByDelegator iterates over all the possible BTC delegations +// filtering by the delegator address (uses the BTCDelegatorToFPKey keystore) +// It stops if the `it` function returns an error +func (k Keeper) iterBtcDelegationsByDelegator(ctx context.Context, del sdk.AccAddress, it func(del, fp sdk.AccAddress) error) error { + st := k.storeBTCDelegatorToFp(ctx, del) + + iter := st.Iterator(nil, nil) + defer iter.Close() + + for ; iter.Valid(); iter.Next() { + fp := sdk.AccAddress(iter.Key()) + if err := it(del, fp); err != nil { + return err + } + } + return nil +} + +// deleteBTCDelegatorToFP deletes one key (del, fp) from the store +// without checking if it exists. +func (k Keeper) deleteBTCDelegatorToFP(ctx context.Context, del, fp sdk.AccAddress) { + st := k.storeBTCDelegatorToFp(ctx, del) + st.Delete(fp.Bytes()) +} + +// GetFinalityProviderCurrentRewards returns the Finality Provider current rewards +// based on the FP address key +func (k Keeper) GetFinalityProviderCurrentRewards(ctx context.Context, fp sdk.AccAddress) (types.FinalityProviderCurrentRewards, error) { + value, err := k.FinalityProviderCurrentRewards.Get(ctx, fp.Bytes()) + if err != nil { + return types.FinalityProviderCurrentRewards{}, types.ErrFPCurrentRewardsNotFound + } + return value, nil +} + +// IterateBTCDelegationRewardsTracker iterates over all the delegation rewards tracker by the finality provider. +// It stops if the function `it` returns an error. +func (k Keeper) IterateBTCDelegationRewardsTracker(ctx context.Context, fp sdk.AccAddress, it func(fp, del sdk.AccAddress) error) error { + rng := collections.NewPrefixedPairRange[[]byte, []byte](fp.Bytes()) + return k.BTCDelegationRewardsTracker.Walk(ctx, rng, func(key collections.Pair[[]byte, []byte], value types.BTCDelegationRewardsTracker) (stop bool, err error) { + del := sdk.AccAddress(key.K2()) + if err := it(fp, del); err != nil { + return err != nil, err + } + return false, nil + }) +} + +// deleteKeysFromBTCDelegationRewardsTracker iterates over all the BTC delegation rewards tracker by the finality provider and deletes it. +func (k Keeper) deleteKeysFromBTCDelegationRewardsTracker(ctx context.Context, fp sdk.AccAddress, delKeys [][]byte) { + rng := collections.NewPrefixedPairRange[[]byte, []byte](fp.Bytes()) + err := k.BTCDelegationRewardsTracker.Clear(ctx, rng) + if err != nil { + k.Logger(sdk.UnwrapSDKContext(ctx)).Error("error deleting BTCDelegationRewardsTracker", "error", err) + } + for _, delKey := range delKeys { + k.deleteBTCDelegatorToFP(ctx, sdk.AccAddress(delKey), fp) + } +} + +// GetBTCDelegationRewardsTracker returns the BTCDelegationRewardsTracker based on the delegation key (fp, del) +// It returns an error in case the key is not found. +func (k Keeper) GetBTCDelegationRewardsTracker(ctx context.Context, fp, del sdk.AccAddress) (types.BTCDelegationRewardsTracker, error) { + value, err := k.BTCDelegationRewardsTracker.Get(ctx, collections.Join(fp.Bytes(), del.Bytes())) + if err != nil { + return types.BTCDelegationRewardsTracker{}, types.ErrBTCDelegationRewardsTrackerNotFound + } + return value, nil +} + +// setBTCDelegationRewardsTracker sets a new structure in the store, it fails and returns an error if the rwd fails to marshal. +func (k Keeper) setBTCDelegationRewardsTracker(ctx context.Context, fp, del sdk.AccAddress, rwd types.BTCDelegationRewardsTracker) error { + k.setBTCDelegatorToFP(ctx, del, fp) + return k.BTCDelegationRewardsTracker.Set(ctx, collections.Join(fp.Bytes(), del.Bytes()), rwd) +} + +// setFinalityProviderCurrentRewards sets a new structure in the store, it fails and returns an error if the rwd fails to marshal. +func (k Keeper) setFinalityProviderCurrentRewards(ctx context.Context, fp sdk.AccAddress, rwd types.FinalityProviderCurrentRewards) error { + return k.FinalityProviderCurrentRewards.Set(ctx, fp.Bytes(), rwd) +} + +// deleteAllFromFinalityProviderRwd deletes all the data related to Finality Provider Rewards +// Historical and current from a fp address key. +func (k Keeper) deleteAllFromFinalityProviderRwd(ctx context.Context, fp sdk.AccAddress) { + rng := collections.NewPrefixedPairRange[[]byte, uint64](fp.Bytes()) + err := k.FinalityProviderHistoricalRewards.Clear(ctx, rng) + if err != nil { + k.Logger(sdk.UnwrapSDKContext(ctx)).Error("error deleting FinalityProviderHistoricalRewards", "error", err) + } + + k.deleteFinalityProviderCurrentRewards(ctx, fp) +} + +// deleteFinalityProviderCurrentRewards deletes the current FP reward based on the key received +func (k Keeper) deleteFinalityProviderCurrentRewards(ctx context.Context, fp sdk.AccAddress) { + if err := k.FinalityProviderCurrentRewards.Remove(ctx, fp.Bytes()); err != nil { + k.Logger(sdk.UnwrapSDKContext(ctx)).Error("error deleting FinalityProviderCurrentRewards", "error", err) + } +} + +// GetFinalityProviderHistoricalRewards returns the FinalityProviderHistoricalRewards based on the key (fp, period) +// It returns an error if the key is not found inside the store. +func (k Keeper) GetFinalityProviderHistoricalRewards(ctx context.Context, fp sdk.AccAddress, period uint64) (types.FinalityProviderHistoricalRewards, error) { + value, err := k.FinalityProviderHistoricalRewards.Get(ctx, collections.Join(fp.Bytes(), period)) + if err != nil { + return types.FinalityProviderHistoricalRewards{}, types.ErrFPHistoricalRewardsNotFound + } + return value, nil +} + +// setFinalityProviderHistoricalRewards sets a new value inside the store, it returns an error +// if the marshal of the `rwd` fails. +func (k Keeper) setFinalityProviderHistoricalRewards(ctx context.Context, fp sdk.AccAddress, period uint64, rwd types.FinalityProviderHistoricalRewards) error { + return k.FinalityProviderHistoricalRewards.Set(ctx, collections.Join(fp.Bytes(), period), rwd) +} + +// subDelegationSat subtracts an amount of active stake from the BTCDelegationRewardsTracker +// and the FinalityProviderCurrentRewards. +// There is no need to check if the fp or delegation exists, because they should exist +// otherwise it is probably a programming error calling to subtract the amount of active sat without +// having any sat added in the first place that created the structures. +func (k Keeper) subDelegationSat(ctx context.Context, fp, del sdk.AccAddress, amt sdkmath.Int) error { + btcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + if err != nil { + return err + } + + btcDelRwdTracker.SubTotalActiveSat(amt) + if btcDelRwdTracker.TotalActiveSat.IsNegative() { + return types.ErrBTCDelegationRewardsTrackerNegativeAmount + } + if err := k.setBTCDelegationRewardsTracker(ctx, fp, del, btcDelRwdTracker); err != nil { + return err + } + + return k.subFinalityProviderStaked(ctx, fp, amt) +} + +// subFinalityProviderStaked subtracts an amount of active stake from the +// FinalityProviderCurrentRewards, it errors out if the finality provider does not exist. +func (k Keeper) subFinalityProviderStaked(ctx context.Context, fp sdk.AccAddress, amt sdkmath.Int) error { + fpCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + if err != nil { + return err + } + + fpCurrentRwd.SubTotalActiveSat(amt) + return k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) +} + +// addFinalityProviderStaked increases the total amount of active satoshi to a finality provider +// if it does not exist one current reward in the store, it initializes a new one +// The initialization of a finality provider also stores one historical fp reward. +func (k Keeper) addFinalityProviderStaked(ctx context.Context, fp sdk.AccAddress, amt sdkmath.Int) error { + fpCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + if err != nil { + if !errors.Is(err, types.ErrFPCurrentRewardsNotFound) { + return err + } + + // needs to initialize at this point due to the amount of + // sats for the FP is inside the FinalityProviderCurrentRewards + fpCurrentRwd, err = k.initializeFinalityProvider(ctx, fp) + if err != nil { + return err + } + } + + fpCurrentRwd.AddTotalActiveSat(amt) + return k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) +} + +// addDelegationSat it increases the amount of satoshi staked for the delegation (fp, del) +// and for the finality provider as well, it initializes the finality provider and the +// BTC delegation rewards tracker if it does not exist. +func (k Keeper) addDelegationSat(ctx context.Context, fp, del sdk.AccAddress, amt sdkmath.Int) error { + btcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + if err != nil { + if !errors.Is(err, types.ErrBTCDelegationRewardsTrackerNotFound) { + return err + } + + // first delegation to this pair (fp, del), can start as 0 previous period as it + // it should be updated afterwards with initilize btc delegation + btcDelRwdTracker = types.NewBTCDelegationRewardsTracker(0, sdkmath.ZeroInt()) + } + + btcDelRwdTracker.AddTotalActiveSat(amt) + if err := k.setBTCDelegationRewardsTracker(ctx, fp, del, btcDelRwdTracker); err != nil { + return err + } + + return k.addFinalityProviderStaked(ctx, fp, amt) +} diff --git a/x/incentive/keeper/reward_tracker_store_test.go b/x/incentive/keeper/reward_tracker_store_test.go new file mode 100644 index 000000000..9ce2581df --- /dev/null +++ b/x/incentive/keeper/reward_tracker_store_test.go @@ -0,0 +1,673 @@ +package keeper + +import ( + "math/rand" + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + appparams "github.com/babylonlabs-io/babylon/app/params" + "github.com/babylonlabs-io/babylon/testutil/datagen" + "github.com/babylonlabs-io/babylon/testutil/store" + "github.com/babylonlabs-io/babylon/x/incentive/types" +) + +func FuzzCheckBtcDelegationActivated(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2, del1 := datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress() + + amtActivateFp1Del1 := datagen.RandomInt(r, 10) + 5 + amtActivateFp2Del1 := datagen.RandomInt(r, 4) + 1 + amtActivateBoth := datagen.RandomInt(r, 7) + 3 + + // delegates for both pairs (fp1, del1) (fp2, del1) + err := k.BtcDelegationActivated(ctx, fp1, del1, amtActivateFp1Del1) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp2, del1, amtActivateFp2Del1) + require.NoError(t, err) + + // verifies the amounts + fp1Del1RwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.NoError(t, err) + require.Equal(t, fp1Del1RwdTracker.TotalActiveSat.Uint64(), amtActivateFp1Del1) + + fp2Del1RwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp2, del1) + require.NoError(t, err) + require.Equal(t, fp2Del1RwdTracker.TotalActiveSat.Uint64(), amtActivateFp2Del1) + + // delegates for both pairs again + err = k.BtcDelegationActivated(ctx, fp1, del1, amtActivateBoth) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp2, del1, amtActivateBoth) + require.NoError(t, err) + + // verifies the amounts + fp1Del1RwdTracker, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.NoError(t, err) + require.Equal(t, fp1Del1RwdTracker.TotalActiveSat.Uint64(), amtActivateFp1Del1+amtActivateBoth) + + fp2Del1RwdTracker, err = k.GetBTCDelegationRewardsTracker(ctx, fp2, del1) + require.NoError(t, err) + require.Equal(t, fp2Del1RwdTracker.TotalActiveSat.Uint64(), amtActivateFp2Del1+amtActivateBoth) + }) +} + +func FuzzCheckBtcDelegationUnbonded(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2, del1 := datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress() + + amtToActivate := datagen.RandomInt(r, 10) + 5 + fp1Del1ToUnbond := datagen.RandomInt(r, int(amtToActivate)-1) + 1 + err := k.BtcDelegationUnbonded(ctx, fp1, del1, fp1Del1ToUnbond) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + + // delegates for both pairs (fp1, del1) (fp2, del1) + err = k.BtcDelegationActivated(ctx, fp1, del1, amtToActivate) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp2, del1, amtToActivate) + require.NoError(t, err) + + // unbonds more than it has, should error + err = k.BtcDelegationUnbonded(ctx, fp1, del1, amtToActivate+1) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNegativeAmount.Error()) + + // normally unbonds only part of it + err = k.BtcDelegationUnbonded(ctx, fp1, del1, fp1Del1ToUnbond) + require.NoError(t, err) + + fp1Del1RwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.NoError(t, err) + require.Equal(t, fp1Del1RwdTracker.TotalActiveSat.Uint64(), amtToActivate-fp1Del1ToUnbond) + + // unbonds all + err = k.BtcDelegationUnbonded(ctx, fp2, del1, amtToActivate) + require.NoError(t, err) + + fp2Del1RwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp2, del1) + require.NoError(t, err) + require.True(t, fp2Del1RwdTracker.TotalActiveSat.IsZero()) + }) +} + +func FuzzCheckBTCDelegatorToFP(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + del1, del2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + // only one set + // del1 -> fp1 + k.setBTCDelegatorToFP(ctx, del1, fp1) + count := 0 + err := k.iterBtcDelegationsByDelegator(ctx, del1, func(del, fp sdk.AccAddress) error { + require.Equal(t, del.String(), del1.String()) + require.Equal(t, fp1.String(), fp.String()) + count++ + return nil + }) + require.Equal(t, 1, count) + require.NoError(t, err) + + // restart count every time + // del1 -> fp1, fp2 + k.setBTCDelegatorToFP(ctx, del1, fp2) + count = 0 + err = k.iterBtcDelegationsByDelegator(ctx, del1, func(del, fp sdk.AccAddress) error { + count++ + require.Equal(t, del.String(), del1.String()) + if fp.Equals(fp1) { + require.Equal(t, fp1.String(), fp.String()) + return nil + } + + require.Equal(t, fp2.String(), fp.String()) + return nil + }) + require.Equal(t, 2, count) + require.NoError(t, err) + + // new delegator + // del2 -> fp2 + k.setBTCDelegatorToFP(ctx, del2, fp2) + count = 0 + err = k.iterBtcDelegationsByDelegator(ctx, del2, func(del, fp sdk.AccAddress) error { + count++ + require.Equal(t, del.String(), del2.String()) + require.Equal(t, fp2.String(), fp.String()) + return nil + }) + require.Equal(t, 1, count) + require.NoError(t, err) + + // deletes del1 -> fp1 + // iterates again should only have the del1 -> fp2 + count = 0 + k.deleteBTCDelegatorToFP(ctx, del1, fp1) + err = k.iterBtcDelegationsByDelegator(ctx, del1, func(del, fp sdk.AccAddress) error { + require.Equal(t, del.String(), del1.String()) + require.Equal(t, fp2.String(), fp.String()) + count++ + return nil + }) + require.Equal(t, 1, count) + require.NoError(t, err) + }) +} + +func FuzzCheckBTCDelegationRewardsTracker(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + del1, del2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + // fp1, del1 + err := k.setBTCDelegationRewardsTracker(ctx, fp1, del1, types.NewBTCDelegationRewardsTracker(0, math.NewInt(100))) + require.NoError(t, err) + + count := 0 + err = k.IterateBTCDelegationRewardsTracker(ctx, fp1, func(fp, del sdk.AccAddress) error { + count++ + require.Equal(t, fp1, fp) + require.Equal(t, del1, del) + return nil + }) + require.Equal(t, 1, count) + require.NoError(t, err) + + // fp1, del2 + err = k.setBTCDelegationRewardsTracker(ctx, fp1, del2, types.NewBTCDelegationRewardsTracker(0, math.NewInt(100))) + require.NoError(t, err) + + count = 0 + err = k.IterateBTCDelegationRewardsTracker(ctx, fp1, func(fp, del sdk.AccAddress) error { + count++ + require.Equal(t, fp1, fp) + if del1.Equals(del) { + require.Equal(t, del1, del) + return nil + } + require.Equal(t, del2, del) + return nil + }) + require.Equal(t, 2, count) + require.NoError(t, err) + + // fp2, del1 + amtFp2Del1 := datagen.RandomMathInt(r, 20000) + startPeriodFp2Del1 := datagen.RandomInt(r, 200) + err = k.setBTCDelegationRewardsTracker(ctx, fp2, del1, types.NewBTCDelegationRewardsTracker(startPeriodFp2Del1, amtFp2Del1)) + require.NoError(t, err) + + btcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp2, del1) + require.NoError(t, err) + require.Equal(t, amtFp2Del1.String(), btcDelRwdTracker.TotalActiveSat.String()) + require.Equal(t, startPeriodFp2Del1, btcDelRwdTracker.StartPeriodCumulativeReward) + + count = 0 + err = k.IterateBTCDelegationRewardsTracker(ctx, fp2, func(fp, del sdk.AccAddress) error { + count++ + require.Equal(t, fp2, fp) + require.Equal(t, del1, del) + return nil + }) + require.Equal(t, 1, count) + require.NoError(t, err) + + // check delete fp2 + k.deleteKeysFromBTCDelegationRewardsTracker(ctx, fp2, [][]byte{del1.Bytes()}) + count = 0 + err = k.IterateBTCDelegationRewardsTracker(ctx, fp2, func(fp, del sdk.AccAddress) error { + count++ + return nil + }) + require.Equal(t, 0, count) + require.NoError(t, err) + + // check delete all from fp1 + k.deleteKeysFromBTCDelegationRewardsTracker(ctx, fp1, [][]byte{del1.Bytes(), del2.Bytes()}) + count = 0 + err = k.IterateBTCDelegationRewardsTracker(ctx, fp1, func(fp, del sdk.AccAddress) error { + count++ + return nil + }) + require.Equal(t, 0, count) + require.NoError(t, err) + + _, err = k.GetBTCDelegationRewardsTracker(ctx, fp2, del1) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + }) +} + +func FuzzCheckFinalityProviderCurrentRewards(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + _, err := k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + expectedCurrentRwdFp1 := datagen.GenRandomFinalityProviderCurrentRewards(r) + err = k.setFinalityProviderCurrentRewards(ctx, fp1, expectedCurrentRwdFp1) + require.NoError(t, err) + + currentRwdFp1, err := k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.NoError(t, err) + require.Equal(t, expectedCurrentRwdFp1.CurrentRewards.String(), currentRwdFp1.CurrentRewards.String()) + require.Equal(t, expectedCurrentRwdFp1.TotalActiveSat.String(), currentRwdFp1.TotalActiveSat.String()) + require.Equal(t, expectedCurrentRwdFp1.Period, currentRwdFp1.Period) + + k.deleteAllFromFinalityProviderRwd(ctx, fp1) + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + // sets a new fp + err = k.setFinalityProviderCurrentRewards(ctx, fp2, datagen.GenRandomFinalityProviderCurrentRewards(r)) + require.NoError(t, err) + + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.NoError(t, err) + + k.deleteFinalityProviderCurrentRewards(ctx, fp2) + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + }) +} + +func FuzzCheckFinalityProviderHistoricalRewards(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + fp1Period1 := datagen.RandomInt(r, 10) + _, err := k.GetFinalityProviderHistoricalRewards(ctx, fp1, fp1Period1) + require.EqualError(t, err, types.ErrFPHistoricalRewardsNotFound.Error()) + + expectedHistRwdFp1 := datagen.GenRandomFPHistRwd(r) + err = k.setFinalityProviderHistoricalRewards(ctx, fp1, fp1Period1, expectedHistRwdFp1) + require.NoError(t, err) + + fp1Period1Historical, err := k.GetFinalityProviderHistoricalRewards(ctx, fp1, fp1Period1) + require.NoError(t, err) + require.Equal(t, expectedHistRwdFp1.CumulativeRewardsPerSat.String(), fp1Period1Historical.CumulativeRewardsPerSat.String()) + + // sets multiple historical for fp2 + fp2Period1Historical := datagen.RandomInt(r, 10) + err = k.setFinalityProviderHistoricalRewards(ctx, fp2, fp2Period1Historical, datagen.GenRandomFPHistRwd(r)) + require.NoError(t, err) + fp2Period2Historical := datagen.RandomInt(r, 10) + err = k.setFinalityProviderHistoricalRewards(ctx, fp2, fp2Period2Historical, datagen.GenRandomFPHistRwd(r)) + require.NoError(t, err) + + // sets a new current fp rwd to check the delete all + err = k.setFinalityProviderCurrentRewards(ctx, fp2, datagen.GenRandomFinalityProviderCurrentRewards(r)) + require.NoError(t, err) + + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.NoError(t, err) + + // deleted all from fp2 + k.deleteAllFromFinalityProviderRwd(ctx, fp2) + + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + _, err = k.GetFinalityProviderHistoricalRewards(ctx, fp2, fp2Period1Historical) + require.EqualError(t, err, types.ErrFPHistoricalRewardsNotFound.Error()) + + _, err = k.GetFinalityProviderHistoricalRewards(ctx, fp2, fp2Period2Historical) + require.EqualError(t, err, types.ErrFPHistoricalRewardsNotFound.Error()) + }) +} + +func FuzzCheckSubFinalityProviderStaked(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + amtSub := datagen.RandomMathInt(r, 100) + err := k.subFinalityProviderStaked(ctx, fp1, amtSub) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + fp2Set := datagen.GenRandomFinalityProviderCurrentRewards(r) + err = k.setFinalityProviderCurrentRewards(ctx, fp2, fp2Set) + require.NoError(t, err) + + err = k.subFinalityProviderStaked(ctx, fp2, fp2Set.TotalActiveSat) + require.NoError(t, err) + + fp2CurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.NoError(t, err) + require.True(t, fp2CurrentRwd.TotalActiveSat.IsZero()) + }) +} + +func FuzzCheckSubDelegationSat(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + amtToSub := datagen.RandomMathInt(r, 10000).AddRaw(10) + err := k.subDelegationSat(ctx, fp, del, amtToSub) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + + amtInRwd := amtToSub.AddRaw(120) + err = k.setBTCDelegationRewardsTracker(ctx, fp, del, types.NewBTCDelegationRewardsTracker(1, amtInRwd)) + require.NoError(t, err) + + fpCurrentRwd := datagen.GenRandomFinalityProviderCurrentRewards(r) + fpCurrentRwd.TotalActiveSat = amtInRwd + err = k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) + require.NoError(t, err) + + err = k.subDelegationSat(ctx, fp, del, amtToSub) + require.NoError(t, err) + + expectedAmt := amtInRwd.Sub(amtToSub) + + delRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + require.NoError(t, err) + require.Equal(t, expectedAmt.String(), delRwdTracker.TotalActiveSat.String()) + + fpCurrentRwd, err = k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, expectedAmt.String(), fpCurrentRwd.TotalActiveSat.String()) + }) +} + +func FuzzCheckAddFinalityProviderStaked(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + amtAdded := datagen.RandomMathInt(r, 1000) + err := k.addFinalityProviderStaked(ctx, fp1, amtAdded) + require.NoError(t, err) + + currentRwdFp1, err := k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.NoError(t, err) + require.Equal(t, currentRwdFp1.TotalActiveSat, amtAdded) + require.Equal(t, currentRwdFp1.Period, uint64(1)) + require.Equal(t, currentRwdFp1.CurrentRewards.String(), sdk.NewCoins().String()) + + err = k.addFinalityProviderStaked(ctx, fp1, amtAdded) + require.NoError(t, err) + currentRwdFp1, err = k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.NoError(t, err) + require.Equal(t, currentRwdFp1.TotalActiveSat, amtAdded.MulRaw(2)) + require.Equal(t, currentRwdFp1.Period, uint64(1)) + require.Equal(t, currentRwdFp1.CurrentRewards.String(), sdk.NewCoins().String()) + + currentRwdFp2, err := k.initializeFinalityProvider(ctx, fp2) + require.NoError(t, err) + + rwdOnFp2 := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp2, rwdOnFp2) + require.NoError(t, err) + + require.Equal(t, currentRwdFp2.TotalActiveSat, math.ZeroInt()) + require.Equal(t, currentRwdFp2.Period, uint64(1)) + require.Equal(t, currentRwdFp2.CurrentRewards.String(), sdk.NewCoins().String()) + + amtAddedToFp2 := datagen.RandomMathInt(r, 1000) + err = k.addFinalityProviderStaked(ctx, fp2, amtAddedToFp2) + require.NoError(t, err) + + currentRwdFp2, err = k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.NoError(t, err) + require.Equal(t, currentRwdFp2.TotalActiveSat.String(), amtAddedToFp2.String()) + require.Equal(t, currentRwdFp2.Period, uint64(1)) + require.Equal(t, currentRwdFp2.CurrentRewards.String(), rwdOnFp2.String()) + }) +} + +func FuzzCheckAddDelegationSat(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + fp2 := datagen.GenRandomAddress() + + amtAdded := datagen.RandomMathInt(r, 1000) + err := k.addDelegationSat(ctx, fp1, del, amtAdded) + require.NoError(t, err) + + rwdTrackerFp1Del1, err := k.GetBTCDelegationRewardsTracker(ctx, fp1, del) + require.NoError(t, err) + require.Equal(t, amtAdded.String(), rwdTrackerFp1Del1.TotalActiveSat.String()) + require.Equal(t, uint64(0), rwdTrackerFp1Del1.StartPeriodCumulativeReward) + + currentRwdFp1, err := k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.NoError(t, err) + require.Equal(t, amtAdded.String(), currentRwdFp1.TotalActiveSat.String()) + require.Equal(t, uint64(1), currentRwdFp1.Period) + require.Equal(t, sdk.NewCoins().String(), currentRwdFp1.CurrentRewards.String()) + + currentHistRwdFp1Del1, err := k.GetFinalityProviderHistoricalRewards(ctx, fp1, 0) + require.NoError(t, err) + require.Equal(t, sdk.NewCoins().String(), currentHistRwdFp1Del1.CumulativeRewardsPerSat.String()) + + // add delegation again + err = k.addDelegationSat(ctx, fp1, del, amtAdded) + require.NoError(t, err) + + // just verifies that the amount duplicated, without modifying the periods + rwdTrackerFp1Del1, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del) + require.NoError(t, err) + require.Equal(t, amtAdded.MulRaw(2).String(), rwdTrackerFp1Del1.TotalActiveSat.String()) + require.Equal(t, uint64(0), rwdTrackerFp1Del1.StartPeriodCumulativeReward) + + currentRwdFp1, err = k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.NoError(t, err) + require.Equal(t, amtAdded.MulRaw(2).String(), currentRwdFp1.TotalActiveSat.String()) + require.Equal(t, uint64(1), currentRwdFp1.Period) + require.Equal(t, sdk.NewCoins().String(), currentRwdFp1.CurrentRewards.String()) + + currentHistRwdFp1Del1, err = k.GetFinalityProviderHistoricalRewards(ctx, fp1, 0) + require.NoError(t, err) + require.Equal(t, sdk.NewCoins().String(), currentHistRwdFp1Del1.CumulativeRewardsPerSat.String()) + + // adds delegation sat to already initilialized FP and delegation + // needs to initialize the FP first, then the delegation + fp2CurrentRwd, err := k.initializeFinalityProvider(ctx, fp2) + require.NoError(t, err) + + startingActiveAmt := datagen.RandomMathInt(r, 100) + err = k.setBTCDelegationRewardsTracker(ctx, fp2, del, types.NewBTCDelegationRewardsTracker(fp2CurrentRwd.Period, startingActiveAmt)) + require.NoError(t, err) + + err = k.initializeBTCDelegation(ctx, fp2, del) + require.NoError(t, err) + + err = k.addDelegationSat(ctx, fp2, del, amtAdded) + require.NoError(t, err) + + // verifies the amount added + rwdTrackerFp2Del1, err := k.GetBTCDelegationRewardsTracker(ctx, fp2, del) + require.NoError(t, err) + require.Equal(t, amtAdded.Add(startingActiveAmt).String(), rwdTrackerFp2Del1.TotalActiveSat.String()) + require.Equal(t, uint64(0), rwdTrackerFp2Del1.StartPeriodCumulativeReward) + + currentRwdFp2, err := k.GetFinalityProviderCurrentRewards(ctx, fp2) + require.NoError(t, err) + // since it was artificially set the starting amount of the delegation + // the FP should only have the amount added. + require.Equal(t, amtAdded.String(), currentRwdFp2.TotalActiveSat.String()) + require.Equal(t, uint64(1), currentRwdFp2.Period) + require.Equal(t, sdk.NewCoins().String(), currentRwdFp2.CurrentRewards.String()) + + currentHistRwdFp2Del1, err := k.GetFinalityProviderHistoricalRewards(ctx, fp2, 0) + require.NoError(t, err) + require.Equal(t, sdk.NewCoins().String(), currentHistRwdFp2Del1.CumulativeRewardsPerSat.String()) + }) +} + +func TestAddSubDelegationSat(t *testing.T) { + k, ctx := NewKeeperWithCtx(t) + + fp1, del1 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + fp2, del2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + amtFp1Del1, amtFp1Del2, amtFp2Del2, amtFp2Del1 := math.NewInt(2000), math.NewInt(4000), math.NewInt(500), math.NewInt(700) + + _, err := k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + + // adds 2000 for fp1, del1 + // fp1 => 2000 + // fp1, del1 => 2000 + err = k.addDelegationSat(ctx, fp1, del1, amtFp1Del1) + require.NoError(t, err) + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + + btcDelRwdFp1Del1, err := k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.NoError(t, err) + // if the normal flow with initilize BTC delegation would have been called, + // it would start as 1. + require.Equal(t, btcDelRwdFp1Del1.StartPeriodCumulativeReward, uint64(0)) + + // adds 4000 for fp1, del2 + // fp1 => 6000 + // fp1, del1 => 2000 + // fp1, del2 => 4000 + err = k.addDelegationSat(ctx, fp1, del2, amtFp1Del2) + require.NoError(t, err) + + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1.Add(amtFp1Del2)) + checkFpDelTotalSat(t, ctx, k, fp1, del2, amtFp1Del2) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + + // adds 500 for fp2, del2 + // fp1 => 6000 + // fp2 => 500 + // fp1, del1 => 2000 + // fp1, del2 => 4000 + // fp2, del2 => 500 + err = k.addDelegationSat(ctx, fp2, del2, amtFp2Del2) + require.NoError(t, err) + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1.Add(amtFp1Del2)) + checkFpTotalSat(t, ctx, k, fp2, amtFp2Del2) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + checkFpDelTotalSat(t, ctx, k, fp1, del2, amtFp1Del2) + checkFpDelTotalSat(t, ctx, k, fp2, del2, amtFp2Del2) + + // adds 700 for fp2, del1 + // fp1 => 6000 + // fp2 => 1200 + // fp1, del1 => 2000 + // fp1, del2 => 4000 + // fp2, del1 => 700 + // fp2, del2 => 500 + err = k.addDelegationSat(ctx, fp2, del1, amtFp2Del1) + require.NoError(t, err) + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1.Add(amtFp1Del2)) + checkFpTotalSat(t, ctx, k, fp2, amtFp2Del2.Add(amtFp2Del1)) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + checkFpDelTotalSat(t, ctx, k, fp1, del2, amtFp1Del2) + checkFpDelTotalSat(t, ctx, k, fp2, del1, amtFp2Del1) + checkFpDelTotalSat(t, ctx, k, fp2, del2, amtFp2Del2) + + lastAmtFp1Del2 := math.NewInt(2000) + // adds 2000 for fp1, del2 + // fp1 => 8000 + // fp2 => 1200 + // fp1, del1 => 2000 + // fp1, del2 => 6000 + // fp2, del1 => 700 + // fp2, del2 => 500 + err = k.addDelegationSat(ctx, fp1, del2, lastAmtFp1Del2) + require.NoError(t, err) + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1.Add(amtFp1Del2).Add(lastAmtFp1Del2)) + checkFpTotalSat(t, ctx, k, fp2, amtFp2Del2.Add(amtFp2Del1)) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + checkFpDelTotalSat(t, ctx, k, fp1, del2, amtFp1Del2.Add(lastAmtFp1Del2)) + checkFpDelTotalSat(t, ctx, k, fp2, del1, amtFp2Del1) + checkFpDelTotalSat(t, ctx, k, fp2, del2, amtFp2Del2) + + subAmtFp2Del2 := math.NewInt(350) + // subtract 350 for fp2, del2 + // fp1 => 8000 + // fp2 => 850 + // fp1, del1 => 2000 + // fp1, del2 => 6000 + // fp2, del1 => 700 + // fp2, del2 => 150 + err = k.subDelegationSat(ctx, fp2, del2, subAmtFp2Del2) + require.NoError(t, err) + checkFpTotalSat(t, ctx, k, fp1, amtFp1Del1.Add(amtFp1Del2).Add(lastAmtFp1Del2)) + checkFpTotalSat(t, ctx, k, fp2, amtFp2Del2.Add(amtFp2Del1).Sub(subAmtFp2Del2)) + checkFpDelTotalSat(t, ctx, k, fp1, del1, amtFp1Del1) + checkFpDelTotalSat(t, ctx, k, fp1, del2, amtFp1Del2.Add(lastAmtFp1Del2)) + checkFpDelTotalSat(t, ctx, k, fp2, del1, amtFp2Del1) + checkFpDelTotalSat(t, ctx, k, fp2, del2, amtFp2Del2.Sub(subAmtFp2Del2)) +} + +func checkFpTotalSat(t *testing.T, ctx sdk.Context, k *Keeper, fp sdk.AccAddress, expectedSat math.Int) { + rwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, expectedSat.String(), rwd.TotalActiveSat.String()) +} + +func checkFpDelTotalSat(t *testing.T, ctx sdk.Context, k *Keeper, fp, del sdk.AccAddress, expectedSat math.Int) { + rwd, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + require.NoError(t, err) + require.Equal(t, expectedSat.String(), rwd.TotalActiveSat.String()) +} + +func NewKeeperWithCtx(t *testing.T) (*Keeper, sdk.Context) { + encConf := appparams.DefaultEncodingConfig() + ctx, kvStore := store.NewStoreWithCtx(t, types.ModuleName) + k := NewKeeper(encConf.Codec, kvStore, nil, nil, nil, appparams.AccGov.String(), appparams.AccFeeCollector.String()) + return &k, ctx +} diff --git a/x/incentive/keeper/reward_tracker_test.go b/x/incentive/keeper/reward_tracker_test.go new file mode 100644 index 000000000..d81a911df --- /dev/null +++ b/x/incentive/keeper/reward_tracker_test.go @@ -0,0 +1,568 @@ +package keeper + +import ( + "context" + "math/rand" + "testing" + + "cosmossdk.io/math" + appparams "github.com/babylonlabs-io/babylon/app/params" + "github.com/babylonlabs-io/babylon/testutil/coins" + "github.com/babylonlabs-io/babylon/testutil/datagen" + "github.com/babylonlabs-io/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func FuzzCheckFpSlashed(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2, del1, del2 := datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress() + + // should not error even without any data in the trackers + // because there is no delegation pair (fp, del1) to iterate + err := k.FpSlashed(ctx, fp1) + require.NoError(t, err) + + // for fp1 30% for del1 and 70% for del 2 + del1Fp1Percentage := uint64(30) + del2Fp1Percentage := uint64(70) + err = k.BtcDelegationActivated(ctx, fp1, del1, del1Fp1Percentage) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp1, del2, del2Fp1Percentage) + require.NoError(t, err) + + // for fp2 50/50% for each del + eachDelFp2Percentage := uint64(50) + err = k.BtcDelegationActivated(ctx, fp2, del1, eachDelFp2Percentage) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp2, del2, eachDelFp2Percentage) + require.NoError(t, err) + + rwdFp1 := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp1, rwdFp1) + require.NoError(t, err) + + rwdFp2 := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp2, rwdFp2) + require.NoError(t, err) + + // slashes the fp1 + err = k.FpSlashed(ctx, fp1) + require.NoError(t, err) + + del1Fp1Rwds := coins.CalculatePercentageOfCoins(rwdFp1, del1Fp1Percentage) + del1RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del1) + coins.RequireCoinsDiffInPointOnePercentMargin(t, del1Fp1Rwds, del1RwdGauge.Coins) + + del2Fp1Rwds := coins.CalculatePercentageOfCoins(rwdFp1, del2Fp1Percentage) + del2RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del2) + coins.RequireCoinsDiffInPointOnePercentMargin(t, del2Fp1Rwds, del2RwdGauge.Coins) + + // verifies that everything was deleted + _, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del1) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + _, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del2) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + _, err = k.GetFinalityProviderCurrentRewards(ctx, fp1) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + _, err = k.GetFinalityProviderHistoricalRewards(ctx, fp1, 1) + require.EqualError(t, err, types.ErrFPHistoricalRewardsNotFound.Error()) + + count := 0 + err = k.iterBtcDelegationsByDelegator(ctx, del1, func(del, fp sdk.AccAddress) error { + count++ + return nil + }) + require.NoError(t, err) + require.Equal(t, count, 1) + + count = 0 + err = k.iterBtcDelegationsByDelegator(ctx, del2, func(del, fp sdk.AccAddress) error { + count++ + return nil + }) + require.NoError(t, err) + require.Equal(t, count, 1) + + // checks that nothing affected the rewards related to the other finality provider + // that wasn't slashed. + err = k.sendAllBtcRewardsToGauge(ctx, del1) + require.NoError(t, err) + + fp2RwdForEachDel := coins.CalculatePercentageOfCoins(rwdFp2, eachDelFp2Percentage) + + lastDel1RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del1) + coins.RequireCoinsDiffInPointOnePercentMargin(t, del1Fp1Rwds.Add(fp2RwdForEachDel...), lastDel1RwdGauge.Coins) + + err = k.sendAllBtcRewardsToGauge(ctx, del2) + require.NoError(t, err) + + lastDel2RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del2) + coins.RequireCoinsDiffInPointOnePercentMargin(t, del2Fp1Rwds.Add(fp2RwdForEachDel...), lastDel2RwdGauge.Coins) + }) +} + +func FuzzCheckSendAllBtcRewardsToGauge(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp1, fp2, del1, del2 := datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress(), datagen.GenRandomAddress() + + // should not error even without any data in the trackers + // because there is no delegation pair (fp, del1) to iterate + err := k.sendAllBtcRewardsToGauge(ctx, del1) + require.NoError(t, err) + + // for fp1 30% for del1 and 70% for del 2 + del1Fp1Percentage := uint64(30) + del2Fp1Percentage := uint64(70) + err = k.BtcDelegationActivated(ctx, fp1, del1, del1Fp1Percentage) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp1, del2, del2Fp1Percentage) + require.NoError(t, err) + + // for fp2 50/50% for each del + eachDelFp2Percentage := uint64(50) + err = k.BtcDelegationActivated(ctx, fp2, del1, eachDelFp2Percentage) + require.NoError(t, err) + err = k.BtcDelegationActivated(ctx, fp2, del2, eachDelFp2Percentage) + require.NoError(t, err) + + rwdFp1 := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp1, rwdFp1) + require.NoError(t, err) + + rwdFp2 := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp2, rwdFp2) + require.NoError(t, err) + + // calculates rewards for the del1 first + err = k.sendAllBtcRewardsToGauge(ctx, del1) + require.NoError(t, err) + + del1Fp1Rwds := coins.CalculatePercentageOfCoins(rwdFp1, del1Fp1Percentage) + fp2RwdForEachDel := coins.CalculatePercentageOfCoins(rwdFp2, eachDelFp2Percentage) + expectedRwdDel1 := del1Fp1Rwds.Add(fp2RwdForEachDel...) + del1RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del1) + coins.RequireCoinsDiffInPointOnePercentMargin(t, expectedRwdDel1, del1RwdGauge.Coins) + + // calculates rewards for the del2 + err = k.sendAllBtcRewardsToGauge(ctx, del2) + require.NoError(t, err) + + del2Fp1Rwds := coins.CalculatePercentageOfCoins(rwdFp1, del2Fp1Percentage) + expectedRwdDel2 := del2Fp1Rwds.Add(fp2RwdForEachDel...) + del2RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del2) + coins.RequireCoinsDiffInPointOnePercentMargin(t, expectedRwdDel2, del2RwdGauge.Coins) + + // check if send all the rewards again something changes, it shouldn't + err = k.sendAllBtcRewardsToGauge(ctx, del1) + require.NoError(t, err) + + newDel1RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del1) + require.Equal(t, newDel1RwdGauge.Coins.String(), del1RwdGauge.Coins.String()) + + // sends new rewards for fp2 which is 50/50 + rwdFp2 = datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp2, rwdFp2) + require.NoError(t, err) + + err = k.sendAllBtcRewardsToGauge(ctx, del1) + require.NoError(t, err) + + lastFp2RwdForEachDel := coins.CalculatePercentageOfCoins(rwdFp2, eachDelFp2Percentage) + lastDel1RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del1) + lastExpectedRwdDel1 := del1Fp1Rwds.Add(fp2RwdForEachDel...).Add(lastFp2RwdForEachDel...) + coins.RequireCoinsDiffInPointOnePercentMargin(t, lastExpectedRwdDel1, lastDel1RwdGauge.Coins) + require.Equal(t, lastFp2RwdForEachDel.String(), lastDel1RwdGauge.Coins.Sub(newDel1RwdGauge.Coins...).String()) + }) +} + +func FuzzCheckBtcDelegationModifiedWithPreInitDel(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + count := 0 + fCount := func(ctx context.Context, fp, del sdk.AccAddress) error { + count++ + return nil + } + require.Equal(t, count, 0) + + err := k.btcDelegationModifiedWithPreInitDel(ctx, fp, del, fCount) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + + err = k.BtcDelegationActivated(ctx, fp, del, datagen.RandomInt(r, 1000)+10) + require.NoError(t, err) + + delRwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del) + require.Nil(t, delRwdGauge) + + coinsToDel := datagen.GenRandomCoins(r) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp, coinsToDel) + require.NoError(t, err) + + err = k.btcDelegationModifiedWithPreInitDel(ctx, fp, del, fCount) + require.NoError(t, err) + require.Equal(t, count, 2) + + delRwdGauge = k.GetRewardGauge(ctx, types.BTCDelegationType, del) + coins.RequireCoinsDiffInPointOnePercentMargin(t, delRwdGauge.Coins, coinsToDel) + // note: the difference here in one micro coin value is expected due to the loss of precision in the BTC reward tracking mechanism + // that needs to keep track of how much rewards 1 satoshi is entitled to receive. + // expected: "10538986AnIGK,10991059BQZFY,19858803DTFwK,18591052NPLYN,11732268RmOWl,17440819TMPYN,17161570WfgTh,17743833aMJHg,19321764evLoF,17692017eysTF,15763155fbRbV,15691503jtkIM,15782745onTeQ,19076817pycDX,10059521tfcfY,13053824tgYdv,16164439ufZLL,15295587xvzKC" + // actual : "10538987AnIGK,10991060BQZFY,19858804DTFwK,18591053NPLYN,11732269RmOWl,17440820TMPYN,17161571WfgTh,17743834aMJHg,19321765evLoF,17692018eysTF,15763156fbRbV,15691504jtkIM,15782746onTeQ,19076818pycDX,10059522tfcfY,13053825tgYdv,16164440ufZLL,15295588xvzKC" + // require.Equal(t, delRwdGauge.Coins.String(), coinsToDel.String()) + }) +} + +func FuzzCheckCalculateBTCDelegationRewardsAndSendToGauge(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + btcRwd := datagen.GenRandomBTCDelegationRewardsTracker(r) + err := k.setBTCDelegationRewardsTracker(ctx, fp, del, btcRwd) + require.NoError(t, err) + + startHist, endHist := datagen.GenRandomFPHistRwdStartAndEnd(r) + err = k.setFinalityProviderHistoricalRewards(ctx, fp, btcRwd.StartPeriodCumulativeReward, startHist) + require.NoError(t, err) + endPeriod := btcRwd.StartPeriodCumulativeReward + datagen.RandomInt(r, 10) + 1 + err = k.setFinalityProviderHistoricalRewards(ctx, fp, endPeriod, endHist) + require.NoError(t, err) + + expectedRwd := endHist.CumulativeRewardsPerSat.Sub(startHist.CumulativeRewardsPerSat...) + expectedRwd = expectedRwd.MulInt(btcRwd.TotalActiveSat) + expectedRwd = expectedRwd.QuoInt(types.DecimalAccumulatedRewards) + + rwdGauge := datagen.GenRandomRewardGauge(r) + k.SetRewardGauge(ctx, types.BTCDelegationType, del, rwdGauge) + + err = k.CalculateBTCDelegationRewardsAndSendToGauge(ctx, fp, del, endPeriod) + require.NoError(t, err) + + delRwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del) + require.Equal(t, rwdGauge.Coins.Add(expectedRwd...).String(), delRwdGauge.Coins.String()) + }) +} + +func FuzzCheckCalculateBTCDelegationRewards(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + btcRwd := datagen.GenRandomBTCDelegationRewardsTracker(r) + + rwd, err := k.CalculateBTCDelegationRewards(ctx, fp, del, btcRwd.StartPeriodCumulativeReward) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + require.Equal(t, rwd, sdk.Coins{}) + + btcRwd.TotalActiveSat = math.ZeroInt() + err = k.setBTCDelegationRewardsTracker(ctx, fp, del, btcRwd) + require.NoError(t, err) + + rwd, err = k.CalculateBTCDelegationRewards(ctx, fp, del, btcRwd.StartPeriodCumulativeReward) + require.NoError(t, err) + require.Equal(t, rwd, sdk.NewCoins()) + + // sets a real one but with a bad period + btcRwd = datagen.GenRandomBTCDelegationRewardsTracker(r) + err = k.setBTCDelegationRewardsTracker(ctx, fp, del, btcRwd) + require.NoError(t, err) + + badEndedPeriod := btcRwd.StartPeriodCumulativeReward - 1 + require.Panics(t, func() { + _, _ = k.CalculateBTCDelegationRewards(ctx, fp, del, badEndedPeriod) + }) + + // Creates a correct and expected historical periods with start and ending properly set + btcRwd = datagen.GenRandomBTCDelegationRewardsTracker(r) + err = k.setBTCDelegationRewardsTracker(ctx, fp, del, btcRwd) + require.NoError(t, err) + + startHist, endHist := datagen.GenRandomFPHistRwdStartAndEnd(r) + err = k.setFinalityProviderHistoricalRewards(ctx, fp, btcRwd.StartPeriodCumulativeReward, startHist) + require.NoError(t, err) + endPeriod := btcRwd.StartPeriodCumulativeReward + datagen.RandomInt(r, 10) + 1 + err = k.setFinalityProviderHistoricalRewards(ctx, fp, endPeriod, endHist) + require.NoError(t, err) + + expectedRwd := endHist.CumulativeRewardsPerSat.Sub(startHist.CumulativeRewardsPerSat...) + expectedRwd = expectedRwd.MulInt(btcRwd.TotalActiveSat) + expectedRwd = expectedRwd.QuoInt(types.DecimalAccumulatedRewards) + + rwd, err = k.CalculateBTCDelegationRewards(ctx, fp, del, endPeriod) + require.NoError(t, err) + require.Equal(t, rwd.String(), expectedRwd.String()) + }) +} + +func FuzzCheckCalculateDelegationRewardsBetween(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp := datagen.GenRandomAddress() + + btcRwd := datagen.GenRandomBTCDelegationRewardsTracker(r) + badEndedPeriod := btcRwd.StartPeriodCumulativeReward - 1 + require.Panics(t, func() { + _, _ = k.calculateDelegationRewardsBetween(ctx, fp, btcRwd, badEndedPeriod) + }) + + historicalStartPeriod := datagen.GenRandomFPHistRwd(r) + historicalStartPeriod.CumulativeRewardsPerSat = historicalStartPeriod.CumulativeRewardsPerSat.MulInt(types.DecimalAccumulatedRewards) + err := k.setFinalityProviderHistoricalRewards(ctx, fp, btcRwd.StartPeriodCumulativeReward, historicalStartPeriod) + require.NoError(t, err) + + endingPeriod := btcRwd.StartPeriodCumulativeReward + 1 + + // creates a bad historical ending period that has less rewards than the starting one + err = k.setFinalityProviderHistoricalRewards(ctx, fp, endingPeriod, types.NewFinalityProviderHistoricalRewards(historicalStartPeriod.CumulativeRewardsPerSat.QuoInt(math.NewInt(2)))) + require.NoError(t, err) + require.Panics(t, func() { + _, _ = k.calculateDelegationRewardsBetween(ctx, fp, btcRwd, endingPeriod) + }) + + // creates a correct historical rewards that has more rewards than the historical + historicalEndingPeriod := datagen.GenRandomFPHistRwd(r) + historicalEndingPeriod.CumulativeRewardsPerSat = historicalEndingPeriod.CumulativeRewardsPerSat.MulInt(types.DecimalAccumulatedRewards) + historicalEndingPeriod.CumulativeRewardsPerSat = historicalEndingPeriod.CumulativeRewardsPerSat.Add(historicalStartPeriod.CumulativeRewardsPerSat...) + err = k.setFinalityProviderHistoricalRewards(ctx, fp, endingPeriod, historicalEndingPeriod) + require.NoError(t, err) + + expectedRewards := historicalEndingPeriod.CumulativeRewardsPerSat.Sub(historicalStartPeriod.CumulativeRewardsPerSat...) + expectedRewards = expectedRewards.MulInt(btcRwd.TotalActiveSat) + expectedRewards = expectedRewards.QuoInt(types.DecimalAccumulatedRewards) + + delRewards, err := k.calculateDelegationRewardsBetween(ctx, fp, btcRwd, endingPeriod) + require.NoError(t, err) + require.Equal(t, expectedRewards.String(), delRewards.String()) + }) +} + +func FuzzCheckAddFinalityProviderRewardsForBtcDelegations(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp := datagen.GenRandomAddress() + + coinsAdded := datagen.GenRandomCoins(r) + // add rewards without initiliaze should error out + err := k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp, coinsAdded) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + _, err = k.initializeFinalityProvider(ctx, fp) + require.NoError(t, err) + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp, coinsAdded) + require.NoError(t, err) + + currentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, coinsAdded.String(), currentRwd.CurrentRewards.String()) + + // adds again the same amounts + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp, coinsAdded) + require.NoError(t, err) + + currentRwd, err = k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, coinsAdded.MulInt(math.NewInt(2)).String(), currentRwd.CurrentRewards.String()) + }) +} + +func FuzzCheckIncrementFinalityProviderPeriod(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp := datagen.GenRandomAddress() + + // increment without initializing the FP + endedPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fp) + require.NoError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + require.Equal(t, endedPeriod, uint64(1)) + + fpCurrentRwd := datagen.GenRandomFinalityProviderCurrentRewards(r) + err = k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) + require.NoError(t, err) + + amtRwdInHistorical := fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards).QuoInt(math.NewInt(2)) + err = k.setFinalityProviderHistoricalRewards(ctx, fp, fpCurrentRwd.Period-1, types.NewFinalityProviderHistoricalRewards(amtRwdInHistorical)) + require.NoError(t, err) + + endedPeriod, err = k.IncrementFinalityProviderPeriod(ctx, fp) + require.NoError(t, err) + require.Equal(t, endedPeriod, fpCurrentRwd.Period) + + historicalEndedPeriod, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, endedPeriod) + require.NoError(t, err) + + expectedHistoricalRwd := amtRwdInHistorical.Add(fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards).QuoInt(fpCurrentRwd.TotalActiveSat)...) + require.Equal(t, historicalEndedPeriod.CumulativeRewardsPerSat.String(), expectedHistoricalRwd.String()) + + newFPCurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, newFPCurrentRwd.CurrentRewards.String(), sdk.NewCoins().String()) + require.Equal(t, newFPCurrentRwd.Period, fpCurrentRwd.Period+1) + require.Equal(t, newFPCurrentRwd.TotalActiveSat, fpCurrentRwd.TotalActiveSat) + }) +} + +func FuzzCheckInitializeBTCDelegation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + r := rand.New(rand.NewSource(seed)) + + k, ctx := NewKeeperWithCtx(t) + fp, del := datagen.GenRandomAddress(), datagen.GenRandomAddress() + + err := k.initializeBTCDelegation(ctx, fp, del) + require.EqualError(t, err, types.ErrFPCurrentRewardsNotFound.Error()) + + fpCurrentRwd := datagen.GenRandomFinalityProviderCurrentRewards(r) + err = k.setFinalityProviderCurrentRewards(ctx, fp, fpCurrentRwd) + require.NoError(t, err) + + err = k.initializeBTCDelegation(ctx, fp, del) + require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error()) + + delBtcRwdTrackerBeforeInitialize := datagen.GenRandomBTCDelegationRewardsTracker(r) + err = k.setBTCDelegationRewardsTracker(ctx, fp, del, delBtcRwdTrackerBeforeInitialize) + require.NoError(t, err) + + err = k.initializeBTCDelegation(ctx, fp, del) + require.NoError(t, err) + + actBtcDelRwdTracker, err := k.GetBTCDelegationRewardsTracker(ctx, fp, del) + require.NoError(t, err) + require.Equal(t, fpCurrentRwd.Period-1, actBtcDelRwdTracker.StartPeriodCumulativeReward) + require.Equal(t, delBtcRwdTrackerBeforeInitialize.TotalActiveSat, actBtcDelRwdTracker.TotalActiveSat) + }) +} + +func FuzzCheckInitializeFinalityProvider(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + t.Parallel() + + k, ctx := NewKeeperWithCtx(t) + fp := datagen.GenRandomAddress() + + currentRwdFp, err := k.initializeFinalityProvider(ctx, fp) + require.NoError(t, err) + require.Equal(t, currentRwdFp.CurrentRewards.String(), sdk.NewCoins().String()) + require.Equal(t, currentRwdFp.TotalActiveSat.String(), math.ZeroInt().String()) + require.Equal(t, currentRwdFp.Period, uint64(1)) + + histRwdFp, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, 0) + require.NoError(t, err) + require.Equal(t, histRwdFp.CumulativeRewardsPerSat.String(), sdk.NewCoins().String()) + + // if initializes it again, the values should be the same + currentRwdFp, err = k.initializeFinalityProvider(ctx, fp) + require.NoError(t, err) + require.Equal(t, currentRwdFp.CurrentRewards.String(), sdk.NewCoins().String()) + require.Equal(t, currentRwdFp.TotalActiveSat.String(), math.ZeroInt().String()) + require.Equal(t, currentRwdFp.Period, uint64(1)) + + histRwdFp, err = k.GetFinalityProviderHistoricalRewards(ctx, fp, 0) + require.NoError(t, err) + require.Equal(t, histRwdFp.CumulativeRewardsPerSat.String(), sdk.NewCoins().String()) + }) +} + +func TestIncrementFinalityProviderPeriod(t *testing.T) { + k, ctx := NewKeeperWithCtx(t) + + fp1, fp2 := datagen.GenRandomAddress(), datagen.GenRandomAddress() + del1 := datagen.GenRandomAddress() + + fp1EndedPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fp1) + require.NoError(t, err) + require.Equal(t, fp1EndedPeriod, uint64(1)) + + checkFpCurrentRwd(t, ctx, k, fp1, fp1EndedPeriod, sdk.NewCoins(), math.NewInt(0)) + checkFpHistoricalRwd(t, ctx, k, fp1, 0, sdk.NewCoins()) + + rwdAddedToPeriod1 := newBaseCoins(2_000000) // 2bbn + err = k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp1, rwdAddedToPeriod1) + require.NoError(t, err) + + // historical should not modify the rewards for the period already created + checkFpHistoricalRwd(t, ctx, k, fp1, 0, sdk.NewCoins()) + checkFpCurrentRwd(t, ctx, k, fp1, fp1EndedPeriod, rwdAddedToPeriod1, math.NewInt(0)) + + // needs to add some voting power so it can calculate the amount of rewards per share + satsDelegated := math.NewInt(500) + err = k.addDelegationSat(ctx, fp1, del1, satsDelegated) + require.NoError(t, err) + + fp1EndedPeriod, err = k.IncrementFinalityProviderPeriod(ctx, fp1) + require.NoError(t, err) + require.Equal(t, fp1EndedPeriod, uint64(1)) + + // now the historical that just ended should have as cumulative rewards 4000ubbn 2_000000ubbn/500sats + checkFpHistoricalRwd(t, ctx, k, fp1, fp1EndedPeriod, newBaseCoins(4000).MulInt(types.DecimalAccumulatedRewards)) + checkFpCurrentRwd(t, ctx, k, fp1, fp1EndedPeriod+1, sdk.NewCoins(), satsDelegated) + + fp2EndedPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fp2) + require.NoError(t, err) + require.Equal(t, fp2EndedPeriod, uint64(1)) +} + +func checkFpHistoricalRwd(t *testing.T, ctx sdk.Context, k *Keeper, fp sdk.AccAddress, period uint64, expectedRwd sdk.Coins) { + historical, err := k.GetFinalityProviderHistoricalRewards(ctx, fp, period) + require.NoError(t, err) + require.Equal(t, historical.CumulativeRewardsPerSat.String(), expectedRwd.String()) +} + +func checkFpCurrentRwd(t *testing.T, ctx sdk.Context, k *Keeper, fp sdk.AccAddress, expectedPeriod uint64, expectedRwd sdk.Coins, totalActiveSat math.Int) { + fp1CurrentRwd, err := k.GetFinalityProviderCurrentRewards(ctx, fp) + require.NoError(t, err) + require.Equal(t, fp1CurrentRwd.CurrentRewards.String(), expectedRwd.String()) + require.Equal(t, fp1CurrentRwd.Period, expectedPeriod) + require.Equal(t, fp1CurrentRwd.TotalActiveSat.String(), totalActiveSat.String()) +} + +func newBaseCoins(amt uint64) sdk.Coins { + return sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewIntFromUint64(amt))) +} diff --git a/x/incentive/keeper/store.go b/x/incentive/keeper/store.go index e93b1573a..53b148ac7 100644 --- a/x/incentive/keeper/store.go +++ b/x/incentive/keeper/store.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "github.com/babylonlabs-io/babylon/x/incentive/types" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go index ccf8a9dab..e19195852 100644 --- a/x/incentive/types/errors.go +++ b/x/incentive/types/errors.go @@ -6,7 +6,11 @@ import ( // x/incentive module sentinel errors var ( - ErrBTCStakingGaugeNotFound = errorsmod.Register(ModuleName, 1100, "BTC staking gauge not found") - ErrRewardGaugeNotFound = errorsmod.Register(ModuleName, 1101, "reward gauge not found") - ErrNoWithdrawableCoins = errorsmod.Register(ModuleName, 1102, "no coin is withdrawable") + ErrBTCStakingGaugeNotFound = errorsmod.Register(ModuleName, 1100, "BTC staking gauge not found") + ErrRewardGaugeNotFound = errorsmod.Register(ModuleName, 1101, "reward gauge not found") + ErrNoWithdrawableCoins = errorsmod.Register(ModuleName, 1102, "no coin is withdrawable") + ErrFPCurrentRewardsNotFound = errorsmod.Register(ModuleName, 1103, "finality provider current rewards not found") + ErrFPHistoricalRewardsNotFound = errorsmod.Register(ModuleName, 1104, "finality provider historical rewards not found") + ErrBTCDelegationRewardsTrackerNotFound = errorsmod.Register(ModuleName, 1105, "BTC delegation rewards tracker not found") + ErrBTCDelegationRewardsTrackerNegativeAmount = errorsmod.Register(ModuleName, 1106, "BTC delegation rewards tracker has a negative amount of TotalActiveSat") ) diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go index 75331f5a7..aeb783b96 100644 --- a/x/incentive/types/keys.go +++ b/x/incentive/types/keys.go @@ -21,11 +21,15 @@ const ( ) var ( - ParamsKey = []byte{0x01} // key prefix for the parameters - BTCStakingGaugeKey = []byte{0x02} // key prefix for BTC staking gauge at each height - DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address - RewardGaugeKey = []byte{0x04} // key prefix for reward gauge for a given stakeholder in a given type - RefundableMsgKeySetPrefix = collections.NewPrefix(5) // key prefix for refundable msg key set + ParamsKey = []byte{0x01} // key prefix for the parameters + BTCStakingGaugeKey = []byte{0x02} // key prefix for BTC staking gauge at each height + DelegatorWithdrawAddrPrefix = []byte{0x03} // key for delegator withdraw address + RewardGaugeKey = []byte{0x04} // key prefix for reward gauge for a given stakeholder in a given type + RefundableMsgKeySetPrefix = collections.NewPrefix(5) // key prefix for refundable msg key set + FinalityProviderCurrentRewardsKeyPrefix = collections.NewPrefix(6) // key prefix for storing the Current rewards of finality provider by addr + FinalityProviderHistoricalRewardsKeyPrefix = collections.NewPrefix(7) // key prefix for storing the Historical rewards of finality provider by addr and period + BTCDelegationRewardsTrackerKeyPrefix = collections.NewPrefix(8) // key prefix for BTC delegation rewards tracker info (del,fp) => BTCDelegationRewardsTracker + BTCDelegatorToFPKey = []byte{0x9} // key prefix for storing the map reference from delegation to finality provider (del) => fp ) // GetWithdrawAddrKey creates the key for a delegator's withdraw addr. diff --git a/x/incentive/types/rewards.go b/x/incentive/types/rewards.go new file mode 100644 index 000000000..f5a71c284 --- /dev/null +++ b/x/incentive/types/rewards.go @@ -0,0 +1,59 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + // it is needed to add decimal points when reducing the rewards amount + // per sat to latter when giving out the rewards to the gauge, reduce + // the decimal points back, currently 20 decimal points are being added + // the sdkmath.Int holds a big int which support up to 2^256 integers + DecimalAccumulatedRewards, _ = sdkmath.NewIntFromString("100000000000000000000") +) + +func NewBTCDelegationRewardsTracker(startPeriod uint64, totalSat sdkmath.Int) BTCDelegationRewardsTracker { + return BTCDelegationRewardsTracker{ + StartPeriodCumulativeReward: startPeriod, + TotalActiveSat: totalSat, + } +} + +func NewFinalityProviderCurrentRewards(currentRewards sdk.Coins, period uint64, totalActiveSatFP sdkmath.Int) FinalityProviderCurrentRewards { + return FinalityProviderCurrentRewards{ + CurrentRewards: currentRewards, + Period: period, + TotalActiveSat: totalActiveSatFP, + } +} + +func NewFinalityProviderHistoricalRewards(cumulativeRewardsPerSat sdk.Coins) FinalityProviderHistoricalRewards { + return FinalityProviderHistoricalRewards{ + CumulativeRewardsPerSat: cumulativeRewardsPerSat, + } +} + +func (f *FinalityProviderCurrentRewards) AddRewards(coinsToAdd sdk.Coins) { + f.CurrentRewards = f.CurrentRewards.Add(coinsToAdd...) +} + +func (f *FinalityProviderCurrentRewards) SubRewards(coinsToSubtract sdk.Coins) { + f.CurrentRewards = f.CurrentRewards.Sub(coinsToSubtract...) +} + +func (f *FinalityProviderCurrentRewards) AddTotalActiveSat(amt sdkmath.Int) { + f.TotalActiveSat = f.TotalActiveSat.Add(amt) +} + +func (f *FinalityProviderCurrentRewards) SubTotalActiveSat(amt sdkmath.Int) { + f.TotalActiveSat = f.TotalActiveSat.Sub(amt) +} + +func (f *BTCDelegationRewardsTracker) AddTotalActiveSat(amt sdkmath.Int) { + f.TotalActiveSat = f.TotalActiveSat.Add(amt) +} + +func (f *BTCDelegationRewardsTracker) SubTotalActiveSat(amt sdkmath.Int) { + f.TotalActiveSat = f.TotalActiveSat.Sub(amt) +} diff --git a/x/incentive/types/rewards.pb.go b/x/incentive/types/rewards.pb.go new file mode 100644 index 000000000..f026a5e11 --- /dev/null +++ b/x/incentive/types/rewards.pb.go @@ -0,0 +1,848 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/rewards.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// FinalityProviderHistoricalRewards represents the cumulative rewards ratio of the +// finality provider per sat in that period. +// The period is ommited here and should be part of the key used to store this structure. +// Key: Prefix + Finality provider bech32 address + Period. +type FinalityProviderHistoricalRewards struct { + // The cumulative rewards of that finality provider per sat until that period + // This coins will aways increase the value, never be reduced due to keep acumulation + // and when the cumulative rewards will be used to distribute rewards, 2 periods will + // be loaded, calculate the difference and multiplied by the total sat amount delegated + // https://github.com/cosmos/cosmos-sdk/blob/e76102f885b71fd6e1c1efb692052173c4b3c3a3/x/distribution/keeper/delegation.go#L47 + CumulativeRewardsPerSat github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=cumulative_rewards_per_sat,json=cumulativeRewardsPerSat,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"cumulative_rewards_per_sat"` +} + +func (m *FinalityProviderHistoricalRewards) Reset() { *m = FinalityProviderHistoricalRewards{} } +func (m *FinalityProviderHistoricalRewards) String() string { return proto.CompactTextString(m) } +func (*FinalityProviderHistoricalRewards) ProtoMessage() {} +func (*FinalityProviderHistoricalRewards) Descriptor() ([]byte, []int) { + return fileDescriptor_fa5a587351117eb0, []int{0} +} +func (m *FinalityProviderHistoricalRewards) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FinalityProviderHistoricalRewards) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FinalityProviderHistoricalRewards.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FinalityProviderHistoricalRewards) XXX_Merge(src proto.Message) { + xxx_messageInfo_FinalityProviderHistoricalRewards.Merge(m, src) +} +func (m *FinalityProviderHistoricalRewards) XXX_Size() int { + return m.Size() +} +func (m *FinalityProviderHistoricalRewards) XXX_DiscardUnknown() { + xxx_messageInfo_FinalityProviderHistoricalRewards.DiscardUnknown(m) +} + +var xxx_messageInfo_FinalityProviderHistoricalRewards proto.InternalMessageInfo + +func (m *FinalityProviderHistoricalRewards) GetCumulativeRewardsPerSat() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.CumulativeRewardsPerSat + } + return nil +} + +// FinalityProviderCurrentRewards represents the current rewards of the pool of +// BTC delegations that delegated for this finality provider is entitled to. +// Note: This rewards are for the BTC delegators that delegated to this FP +// the FP itself is not the owner or can withdraw this rewards. +// If a slash event happens with this finality provider, all the delegations need +// to withdraw to the RewardGauge and the related scrutures should be deleted. +// Key: Prefix + Finality provider bech32 address. +type FinalityProviderCurrentRewards struct { + // CurrentRewards is the current rewards that the finality provider have and it was not + // yet stored inside the FinalityProviderHistoricalRewards. Once something happens that + // modifies the amount of satoshis delegated to this finality provider or the delegators + // starting period (activation, unbonding or btc rewards withdraw) + // a new period must be created, accumulate this rewards to FinalityProviderHistoricalRewards + // with a new period and zero out the Current Rewards. + CurrentRewards github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=current_rewards,json=currentRewards,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"current_rewards"` + // Period stores the current period that serves as a reference for + // creating new historical rewards and correlate with BTCDelegationRewardsTracker + // StartPeriodCumulativeReward. + Period uint64 `protobuf:"varint,2,opt,name=period,proto3" json:"period,omitempty"` + // TotalActiveSat is the total amount of active satoshi delegated + // to this finality provider. + TotalActiveSat cosmossdk_io_math.Int `protobuf:"bytes,3,opt,name=total_active_sat,json=totalActiveSat,proto3,customtype=cosmossdk.io/math.Int" json:"total_active_sat"` +} + +func (m *FinalityProviderCurrentRewards) Reset() { *m = FinalityProviderCurrentRewards{} } +func (m *FinalityProviderCurrentRewards) String() string { return proto.CompactTextString(m) } +func (*FinalityProviderCurrentRewards) ProtoMessage() {} +func (*FinalityProviderCurrentRewards) Descriptor() ([]byte, []int) { + return fileDescriptor_fa5a587351117eb0, []int{1} +} +func (m *FinalityProviderCurrentRewards) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FinalityProviderCurrentRewards) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FinalityProviderCurrentRewards.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FinalityProviderCurrentRewards) XXX_Merge(src proto.Message) { + xxx_messageInfo_FinalityProviderCurrentRewards.Merge(m, src) +} +func (m *FinalityProviderCurrentRewards) XXX_Size() int { + return m.Size() +} +func (m *FinalityProviderCurrentRewards) XXX_DiscardUnknown() { + xxx_messageInfo_FinalityProviderCurrentRewards.DiscardUnknown(m) +} + +var xxx_messageInfo_FinalityProviderCurrentRewards proto.InternalMessageInfo + +func (m *FinalityProviderCurrentRewards) GetCurrentRewards() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.CurrentRewards + } + return nil +} + +func (m *FinalityProviderCurrentRewards) GetPeriod() uint64 { + if m != nil { + return m.Period + } + return 0 +} + +// BTCDelegationRewardsTracker represents the structure that holds information +// from the last time this BTC delegator withdraw the rewards or modified his +// active staked amount to one finality provider. +// The finality provider address is ommitted here but should be part of the +// key used to store this structure together with the BTC delegator address. +type BTCDelegationRewardsTracker struct { + // StartPeriodCumulativeReward the starting period the the BTC delegator + // made his last withdraw of rewards or modified his active staking amount + // of satoshis. + StartPeriodCumulativeReward uint64 `protobuf:"varint,1,opt,name=start_period_cumulative_reward,json=startPeriodCumulativeReward,proto3" json:"start_period_cumulative_reward,omitempty"` + // TotalActiveSat is the total amount of active satoshi delegated + // to one specific finality provider. + TotalActiveSat cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=total_active_sat,json=totalActiveSat,proto3,customtype=cosmossdk.io/math.Int" json:"total_active_sat"` +} + +func (m *BTCDelegationRewardsTracker) Reset() { *m = BTCDelegationRewardsTracker{} } +func (m *BTCDelegationRewardsTracker) String() string { return proto.CompactTextString(m) } +func (*BTCDelegationRewardsTracker) ProtoMessage() {} +func (*BTCDelegationRewardsTracker) Descriptor() ([]byte, []int) { + return fileDescriptor_fa5a587351117eb0, []int{2} +} +func (m *BTCDelegationRewardsTracker) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCDelegationRewardsTracker) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCDelegationRewardsTracker.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCDelegationRewardsTracker) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegationRewardsTracker.Merge(m, src) +} +func (m *BTCDelegationRewardsTracker) XXX_Size() int { + return m.Size() +} +func (m *BTCDelegationRewardsTracker) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegationRewardsTracker.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCDelegationRewardsTracker proto.InternalMessageInfo + +func (m *BTCDelegationRewardsTracker) GetStartPeriodCumulativeReward() uint64 { + if m != nil { + return m.StartPeriodCumulativeReward + } + return 0 +} + +func init() { + proto.RegisterType((*FinalityProviderHistoricalRewards)(nil), "babylon.incentive.FinalityProviderHistoricalRewards") + proto.RegisterType((*FinalityProviderCurrentRewards)(nil), "babylon.incentive.FinalityProviderCurrentRewards") + proto.RegisterType((*BTCDelegationRewardsTracker)(nil), "babylon.incentive.BTCDelegationRewardsTracker") +} + +func init() { proto.RegisterFile("babylon/incentive/rewards.proto", fileDescriptor_fa5a587351117eb0) } + +var fileDescriptor_fa5a587351117eb0 = []byte{ + // 458 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0xcf, 0x6e, 0x13, 0x31, + 0x10, 0xc6, 0xe3, 0x14, 0xf5, 0x60, 0x50, 0x81, 0x88, 0x3f, 0x69, 0x2a, 0x39, 0xa1, 0xa7, 0x48, + 0x28, 0x6b, 0x4a, 0x9f, 0x80, 0x2c, 0x42, 0xf4, 0x80, 0x14, 0x85, 0x72, 0xe1, 0xb2, 0xf2, 0x7a, + 0xad, 0xd4, 0xca, 0xc6, 0x13, 0xd9, 0xb3, 0x81, 0x3c, 0x00, 0x12, 0x47, 0x9e, 0x83, 0x03, 0x27, + 0x2e, 0xbc, 0x41, 0x8f, 0x15, 0x27, 0xc4, 0xa1, 0xa0, 0xe4, 0x45, 0xd0, 0xda, 0x0e, 0x94, 0xc2, + 0x0d, 0x71, 0xda, 0x9d, 0x9d, 0xcf, 0xf3, 0xfd, 0xf4, 0x79, 0x96, 0x76, 0x73, 0x91, 0x2f, 0x4b, + 0x30, 0x5c, 0x1b, 0xa9, 0x0c, 0xea, 0x85, 0xe2, 0x56, 0xbd, 0x12, 0xb6, 0x70, 0xc9, 0xdc, 0x02, + 0x42, 0xeb, 0x66, 0x14, 0x24, 0x3f, 0x05, 0x9d, 0x5b, 0x13, 0x98, 0x80, 0xef, 0xf2, 0xfa, 0x2d, + 0x08, 0x3b, 0x4c, 0x82, 0x9b, 0x81, 0xe3, 0xb9, 0x70, 0x8a, 0x2f, 0x0e, 0x72, 0x85, 0xe2, 0x80, + 0x4b, 0xd0, 0x26, 0xf6, 0x77, 0x43, 0x3f, 0x0b, 0x07, 0x43, 0x11, 0x5a, 0xfb, 0x1f, 0x08, 0xbd, + 0xf7, 0x44, 0x1b, 0x51, 0x6a, 0x5c, 0x8e, 0x2c, 0x2c, 0x74, 0xa1, 0xec, 0x53, 0xed, 0x10, 0xac, + 0x96, 0xa2, 0x1c, 0x07, 0x9e, 0xd6, 0x5b, 0x42, 0x3b, 0xb2, 0x9a, 0x55, 0xa5, 0xa8, 0x29, 0xb2, + 0x88, 0x99, 0xcd, 0x95, 0xcd, 0x9c, 0xc0, 0x36, 0xe9, 0x6d, 0xf5, 0xaf, 0x3e, 0xdc, 0x4d, 0xe2, + 0xe4, 0x1a, 0x23, 0x89, 0x18, 0x49, 0x0a, 0xda, 0x0c, 0x1f, 0x9c, 0x9e, 0x77, 0x1b, 0xef, 0xbf, + 0x75, 0xfb, 0x13, 0x8d, 0x27, 0x55, 0x9e, 0x48, 0x98, 0x45, 0x8c, 0xf8, 0x18, 0xb8, 0x62, 0xca, + 0x71, 0x39, 0x57, 0xce, 0x1f, 0x70, 0xe3, 0xbb, 0xbf, 0xec, 0x22, 0xc4, 0x48, 0xd9, 0xe7, 0x02, + 0xf7, 0xdf, 0x34, 0x29, 0xbb, 0x0c, 0x9c, 0x56, 0xd6, 0x2a, 0x83, 0x1b, 0x5a, 0xa4, 0xd7, 0x65, + 0xf8, 0xb2, 0x21, 0xfd, 0x1f, 0x84, 0x3b, 0xf2, 0x77, 0xd7, 0x3b, 0x74, 0x7b, 0xae, 0xac, 0x86, + 0xa2, 0xdd, 0xec, 0x91, 0xfe, 0x95, 0x71, 0xac, 0x5a, 0x2f, 0xe8, 0x0d, 0x04, 0x14, 0x65, 0x26, + 0xa4, 0x0f, 0xaf, 0x0e, 0x6c, 0xab, 0x47, 0xfa, 0xd7, 0x86, 0xf7, 0x6b, 0xcf, 0xaf, 0xe7, 0xdd, + 0xdb, 0xc1, 0xc1, 0x15, 0xd3, 0x44, 0x03, 0x9f, 0x09, 0x3c, 0x49, 0x8e, 0x0c, 0x7e, 0xfe, 0x38, + 0xa0, 0x11, 0xf7, 0xc8, 0xe0, 0x78, 0xc7, 0x0f, 0x79, 0xe4, 0x67, 0xd4, 0x39, 0x7c, 0x22, 0x74, + 0x6f, 0x78, 0x9c, 0x3e, 0x56, 0xa5, 0x9a, 0x08, 0xd4, 0x60, 0x22, 0xc7, 0xb1, 0x15, 0x72, 0xaa, + 0x6c, 0x2b, 0xa5, 0xcc, 0xa1, 0xb0, 0x98, 0x05, 0x8c, 0xec, 0x8f, 0xeb, 0x6b, 0x13, 0x8f, 0xb9, + 0xe7, 0x55, 0x23, 0x2f, 0x4a, 0x2f, 0x65, 0xfe, 0x57, 0xf6, 0xe6, 0x3f, 0xb3, 0x0f, 0x9f, 0x9d, + 0xae, 0x18, 0x39, 0x5b, 0x31, 0xf2, 0x7d, 0xc5, 0xc8, 0xbb, 0x35, 0x6b, 0x9c, 0xad, 0x59, 0xe3, + 0xcb, 0x9a, 0x35, 0x5e, 0x1e, 0x5e, 0x88, 0x3f, 0x6e, 0x7f, 0x29, 0x72, 0x37, 0xd0, 0xb0, 0x29, + 0xf9, 0xeb, 0x0b, 0xff, 0x8b, 0xbf, 0x8f, 0x7c, 0xdb, 0xaf, 0xf2, 0xe1, 0x8f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x9d, 0x04, 0xda, 0xdd, 0x51, 0x03, 0x00, 0x00, +} + +func (m *FinalityProviderHistoricalRewards) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FinalityProviderHistoricalRewards) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FinalityProviderHistoricalRewards) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CumulativeRewardsPerSat) > 0 { + for iNdEx := len(m.CumulativeRewardsPerSat) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CumulativeRewardsPerSat[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintRewards(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *FinalityProviderCurrentRewards) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FinalityProviderCurrentRewards) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FinalityProviderCurrentRewards) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.TotalActiveSat.Size() + i -= size + if _, err := m.TotalActiveSat.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintRewards(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Period != 0 { + i = encodeVarintRewards(dAtA, i, uint64(m.Period)) + i-- + dAtA[i] = 0x10 + } + if len(m.CurrentRewards) > 0 { + for iNdEx := len(m.CurrentRewards) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CurrentRewards[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintRewards(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *BTCDelegationRewardsTracker) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCDelegationRewardsTracker) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCDelegationRewardsTracker) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.TotalActiveSat.Size() + i -= size + if _, err := m.TotalActiveSat.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintRewards(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.StartPeriodCumulativeReward != 0 { + i = encodeVarintRewards(dAtA, i, uint64(m.StartPeriodCumulativeReward)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintRewards(dAtA []byte, offset int, v uint64) int { + offset -= sovRewards(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *FinalityProviderHistoricalRewards) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.CumulativeRewardsPerSat) > 0 { + for _, e := range m.CumulativeRewardsPerSat { + l = e.Size() + n += 1 + l + sovRewards(uint64(l)) + } + } + return n +} + +func (m *FinalityProviderCurrentRewards) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.CurrentRewards) > 0 { + for _, e := range m.CurrentRewards { + l = e.Size() + n += 1 + l + sovRewards(uint64(l)) + } + } + if m.Period != 0 { + n += 1 + sovRewards(uint64(m.Period)) + } + l = m.TotalActiveSat.Size() + n += 1 + l + sovRewards(uint64(l)) + return n +} + +func (m *BTCDelegationRewardsTracker) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.StartPeriodCumulativeReward != 0 { + n += 1 + sovRewards(uint64(m.StartPeriodCumulativeReward)) + } + l = m.TotalActiveSat.Size() + n += 1 + l + sovRewards(uint64(l)) + return n +} + +func sovRewards(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozRewards(x uint64) (n int) { + return sovRewards(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *FinalityProviderHistoricalRewards) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FinalityProviderHistoricalRewards: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FinalityProviderHistoricalRewards: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CumulativeRewardsPerSat", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRewards + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRewards + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CumulativeRewardsPerSat = append(m.CumulativeRewardsPerSat, types.Coin{}) + if err := m.CumulativeRewardsPerSat[len(m.CumulativeRewardsPerSat)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRewards(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRewards + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FinalityProviderCurrentRewards) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FinalityProviderCurrentRewards: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FinalityProviderCurrentRewards: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentRewards", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRewards + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRewards + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CurrentRewards = append(m.CurrentRewards, types.Coin{}) + if err := m.CurrentRewards[len(m.CurrentRewards)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Period", wireType) + } + m.Period = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Period |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalActiveSat", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRewards + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthRewards + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TotalActiveSat.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRewards(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRewards + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BTCDelegationRewardsTracker) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCDelegationRewardsTracker: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCDelegationRewardsTracker: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartPeriodCumulativeReward", wireType) + } + m.StartPeriodCumulativeReward = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartPeriodCumulativeReward |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalActiveSat", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRewards + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRewards + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthRewards + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TotalActiveSat.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRewards(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRewards + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRewards(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRewards + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRewards + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRewards + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthRewards + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupRewards + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthRewards + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthRewards = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRewards = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupRewards = fmt.Errorf("proto: unexpected end of group") +)