diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index fe42f288..17c9a522 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -528,7 +528,9 @@ func (ak *AppKeepers) InitKeepers( runtime.NewKVStoreService(keys[btcstakingtypes.StoreKey]), &btclightclientKeeper, &btcCheckpointKeeper, - &checkpointingKeeper, + // setting the finality keeper as nil for now + // need to set it after finality keeper is initiated + nil, btcNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) @@ -539,10 +541,14 @@ func (ak *AppKeepers) InitKeepers( runtime.NewKVStoreService(keys[finalitytypes.StoreKey]), ak.BTCStakingKeeper, ak.IncentiveKeeper, + ak.CheckpointingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ak.BTCStakingKeeper = *ak.BTCStakingKeeper.SetHooks(btcstakingtypes.NewMultiBtcStakingHooks(ak.FinalityKeeper.Hooks())) ak.FinalityKeeper = *ak.FinalityKeeper.SetHooks(finalitytypes.NewMultiFinalityHooks(ak.BTCStakingKeeper.Hooks())) + // TODO this introduces circular dependency between the finality module and + // the btcstaking modules, need refactoring + ak.BTCStakingKeeper.FinalityKeeper = ak.FinalityKeeper // create evidence keeper with router evidenceKeeper := evidencekeeper.NewKeeper( diff --git a/proto/babylon/btcstaking/v1/incentive.proto b/proto/babylon/btcstaking/v1/incentive.proto index 7dcde01f..f849745e 100644 --- a/proto/babylon/btcstaking/v1/incentive.proto +++ b/proto/babylon/btcstaking/v1/incentive.proto @@ -9,9 +9,15 @@ option go_package = "github.com/babylonlabs-io/babylon/x/btcstaking/types"; // VotingPowerDistCache is the cache for voting power distribution of finality providers // and their BTC delegations at a height message VotingPowerDistCache { + option (gogoproto.goproto_getters) = false; + // total_voting_power is the total voting power of all the finality providers + // in the cache uint64 total_voting_power = 1; // finality_providers is a list of finality providers' voting power information repeated FinalityProviderDistInfo finality_providers = 2; + // num_active_fps is the number of finality providers that have active BTC + // delegations as well as timestamped public randomness + uint32 num_active_fps = 3; } // FinalityProviderDistInfo is the reward distribution of a finality provider and its BTC delegations @@ -30,6 +36,9 @@ message FinalityProviderDistInfo { uint64 total_voting_power = 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 + bool is_timestamped = 6; } // BTCDelDistInfo contains the information related to reward distribution for a BTC delegation diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index f6f56f6d..1167e774 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -27,6 +27,8 @@ message PubRandCommit { // commitment is the value of the commitment // currently, it is the root of the merkle tree constructed by the public randomness bytes commitment = 3; + // epoch_num defines the epoch number that the commit falls into + uint64 epoch_num = 4; } // Evidence is the evidence that a finality provider has signed finality diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 7b6c9fb0..159995a6 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -102,6 +102,8 @@ message PubRandCommitResponse { uint64 num_pub_rand = 1; // commitment is the value of the commitment bytes commitment = 2; + // epoch_num defines the epoch number that the commit falls into + uint64 epoch_num = 3; } // QueryListPubRandCommitRequest is the request type for the diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 85f3dff1..d73a7757 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -26,6 +26,7 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/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" ) @@ -243,21 +244,6 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { activeDel := activeDels.Dels[0] s.True(activeDel.HasCovenantQuorums(covenantQuorum)) - - // wait for a block so that above txs take effect and the voting power table - // is updated in the next block's BeginBlock - nonValidatorNode.WaitForNextBlock() - - // ensure BTC staking is activated - activatedHeight := nonValidatorNode.QueryActivatedHeight() - s.Positive(activatedHeight) - // ensure finality provider has voting power at activated height - currentBtcTip, err := nonValidatorNode.QueryTip() - s.NoError(err) - activeFps := nonValidatorNode.QueryActiveFinalityProvidersAtHeight(activatedHeight) - s.Len(activeFps, 1) - s.Equal(activeFps[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) - s.Equal(activeFps[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) } // Test2CommitPublicRandomnessAndSubmitFinalitySignature is an end-to-end @@ -270,15 +256,19 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NoError(err) // get activated height - activatedHeight := nonValidatorNode.QueryActivatedHeight() - s.Positive(activatedHeight) + _, err = nonValidatorNode.QueryActivatedHeight() + s.ErrorContains(err, bstypes.ErrBTCStakingNotActivated.Error()) + fps := nonValidatorNode.QueryFinalityProviders() + s.Len(fps, 1) + s.Zero(fps[0].VotingPower) /* commit a number of public randomness since activatedHeight */ // commit public randomness list numPubRand := uint64(100) - randListInfo, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, fpBTCSK, activatedHeight, numPubRand) + commitStartHeight := uint64(1) + randListInfo, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, fpBTCSK, commitStartHeight, numPubRand) s.NoError(err) nonValidatorNode.CommitPubRandList( msgCommitPubRandList.FpBtcPk, @@ -288,16 +278,6 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat msgCommitPubRandList.Sig, ) - // ensure public randomness list is eventually committed - nonValidatorNode.WaitForNextBlock() - var prCommitMap map[uint64]*ftypes.PubRandCommitResponse - s.Eventually(func() bool { - prCommitMap = nonValidatorNode.QueryListPubRandCommit(cacheFP.BtcPk) - return len(prCommitMap) > 0 - }, time.Minute, time.Second*5) - s.Equal(prCommitMap[activatedHeight].NumPubRand, msgCommitPubRandList.NumPubRand) - s.Equal(prCommitMap[activatedHeight].Commitment, msgCommitPubRandList.Commitment) - // no reward gauge for finality provider and delegation yet fpBabylonAddr, err := sdk.AccAddressFromBech32(cacheFP.Addr) s.NoError(err) @@ -306,6 +286,41 @@ 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) + + // 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) + /* submit finality signature */ @@ -314,7 +329,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NoError(err) appHash := blockToVote.AppHash - idx := 0 + idx := activatedHeight - commitStartHeight msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), appHash...) // generate EOTS signature sig, err := eots.Sign(fpBTCSK, randListInfo.SRList[idx], msgToSign) @@ -324,21 +339,14 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat nonValidatorNode.AddFinalitySig(cacheFP.BtcPk, activatedHeight, &randListInfo.PRList[idx], *randListInfo.ProofList[idx].ToProto(), appHash, eotsSig) // ensure vote is eventually cast - nonValidatorNode.WaitForNextBlock() - var votes []bbn.BIP340PubKey + var finalizedBlocks []*ftypes.IndexedBlock s.Eventually(func() bool { - votes = nonValidatorNode.QueryVotesAtHeight(activatedHeight) - return len(votes) > 0 - }, time.Minute, time.Second*5) - s.Equal(1, len(votes)) - s.Equal(votes[0].MarshalHex(), cacheFP.BtcPk.MarshalHex()) - // once the vote is cast, ensure block is finalised - finalizedBlock := nonValidatorNode.QueryIndexedBlock(activatedHeight) - s.NotEmpty(finalizedBlock) - s.Equal(appHash.Bytes(), finalizedBlock.AppHash) - finalizedBlocks := nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) - s.NotEmpty(finalizedBlocks) + finalizedBlocks = nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) + return len(finalizedBlocks) > 0 + }, time.Minute, time.Millisecond*50) + s.Equal(activatedHeight, finalizedBlocks[0].Height) s.Equal(appHash.Bytes(), finalizedBlocks[0].AppHash) + s.T().Logf("the block %d is finalized", activatedHeight) // ensure finality provider has received rewards after the block is finalised fpRewardGauges, err := nonValidatorNode.QueryRewardGauge(fpBabylonAddr) @@ -352,6 +360,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat btcDelRewardGauge, ok := btcDelRewardGauges[itypes.BTCDelegationType.String()] s.True(ok) s.True(btcDelRewardGauge.Coins.IsAllPositive()) + s.T().Logf("the finality provider received rewards for providing finality") } func (s *BTCStakingTestSuite) Test4WithdrawReward() { diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index d981974d..366bf335 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -163,24 +163,12 @@ func (n *NodeConfig) QueryListSnapshots() ([]*cmtabcitypes.Snapshot, error) { return listSnapshots.Snapshots, nil } -// func (n *NodeConfig) QueryContractsFromId(codeId int) ([]string, error) { -// path := fmt.Sprintf("/cosmwasm/wasm/v1/code/%d/contracts", codeId) -// bz, err := n.QueryGRPCGateway(path) - -// require.NoError(n.t, err) - -// var contractsResponse wasmtypes.QueryContractsByCodeResponse -// if err := util.Cdc.UnmarshalJSON(bz, &contractsResponse); err != nil { -// return nil, err -// } - -// return contractsResponse.Contracts, nil -// } - func (n *NodeConfig) QueryRawCheckpoint(epoch uint64) (*ct.RawCheckpointWithMetaResponse, error) { path := fmt.Sprintf("babylon/checkpointing/v1/raw_checkpoint/%d", epoch) bz, err := n.QueryGRPCGateway(path, url.Values{}) - require.NoError(n.t, err) + if err != nil { + return nil, err + } var checkpointingResponse ct.QueryRawCheckpointResponse if err := util.Cdc.UnmarshalJSON(bz, &checkpointingResponse); err != nil { @@ -208,6 +196,19 @@ func (n *NodeConfig) QueryRawCheckpoints(pagination *query.PageRequest) (*ct.Que return &checkpointingResponse, nil } +func (n *NodeConfig) QueryLastFinalizedEpoch() (uint64, error) { + queryParams := url.Values{} + queryParams.Add("status", fmt.Sprintf("%d", ct.Finalized)) + + bz, err := n.QueryGRPCGateway(fmt.Sprintf("/babylon/checkpointing/v1/last_raw_checkpoint/%d", ct.Finalized), queryParams) + require.NoError(n.t, err) + var res ct.QueryLastCheckpointWithStatusResponse + if err := util.Cdc.UnmarshalJSON(bz, &res); err != nil { + return 0, err + } + return res.RawCheckpoint.EpochNum, nil +} + func (n *NodeConfig) QueryBtcBaseHeader() (*blc.BTCHeaderInfoResponse, error) { bz, err := n.QueryGRPCGateway("babylon/btclightclient/v1/baseheader", url.Values{}) require.NoError(n.t, err) diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index f7a735c8..861ac6a6 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -94,15 +94,19 @@ func (n *NodeConfig) QueryUnbondedDelegations() []*bstypes.BTCDelegationResponse return resp.BtcDelegations } -func (n *NodeConfig) QueryActivatedHeight() uint64 { +func (n *NodeConfig) QueryActivatedHeight() (uint64, error) { bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/activated_height", url.Values{}) - require.NoError(n.t, err) + if err != nil { + return 0, err + } var resp bstypes.QueryActivatedHeightResponse err = util.Cdc.UnmarshalJSON(bz, &resp) - require.NoError(n.t, err) + if err != nil { + return 0, err + } - return resp.Height + return resp.Height, nil } // TODO: pagination support diff --git a/test/e2e/upgrades/signet-launch.json b/test/e2e/upgrades/signet-launch.json index 820686a2..2bd5a9f3 100644 --- a/test/e2e/upgrades/signet-launch.json +++ b/test/e2e/upgrades/signet-launch.json @@ -17,4 +17,4 @@ "title": "any title", "summary": "any summary", "expedited": false -} \ No newline at end of file +} diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index 2cd0e10b..d379e82b 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -4,10 +4,11 @@ import ( "math/rand" sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" itypes "github.com/babylonlabs-io/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -105,6 +106,7 @@ func GenRandomFinalityProviderDistInfo(r *rand.Rand) (*bstypes.FinalityProviderD } fpDistInfo.BtcDels = append(fpDistInfo.BtcDels, btcDelDistInfo) fpDistInfo.TotalVotingPower += btcDelDistInfo.VotingPower + fpDistInfo.IsTimestamped = true } return fpDistInfo, nil } diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index fd00fbf7..a3bcad46 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -27,7 +27,7 @@ func BTCStakingKeeper( t testing.TB, btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper, - ckptKeeper types.CheckpointingKeeper, + finalityKeeper types.FinalityKeeper, ) (*keeper.Keeper, sdk.Context) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) @@ -44,7 +44,7 @@ func BTCStakingKeeper( runtime.NewKVStoreService(storeKey), btclcKeeper, btccKeeper, - ckptKeeper, + finalityKeeper, &chaincfg.SimNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index fc15a866..1b8a2ade 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -22,7 +22,7 @@ import ( "github.com/babylonlabs-io/babylon/x/finality/types" ) -func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types.IncentiveKeeper) (*keeper.Keeper, sdk.Context) { +func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types.IncentiveKeeper, cKeeper types.CheckpointingKeeper) (*keeper.Keeper, sdk.Context) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() @@ -38,6 +38,7 @@ func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types runtime.NewKVStoreService(storeKey), bsKeeper, iKeeper, + cKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/x/btcstaking/README.md b/x/btcstaking/README.md index b07b297d..a54c9395 100644 --- a/x/btcstaking/README.md +++ b/x/btcstaking/README.md @@ -72,7 +72,7 @@ follows: unbonding transaction, and unbonding slashing transaction to Babylon. 3. The covenant committee verifies spending conditions of the staking transaction, and submits its signatures on the BTC staker's transactions. At - this point, the finality provider receives bitcoins and thus voting power + this point, the finality provider receives voting power from the BTC delegation. 4. Upon each new block, the BTC Staking module will record the voting power table of finality providers. @@ -297,6 +297,9 @@ voting power table of all finality providers at each height of the Babylon chain. The key is the block height concatenated with the finality provider's Bitcoin secp256k1 public key in BIP-340 format, and the value is the finality provider's voting power quantified in Satoshis. +Voting power is assigned to top `N` (defined in parameters) finality providers +that have BTC-timestamped public randomness for the height, ranked by the total +delegated value. ### Params @@ -406,9 +409,7 @@ Upon `MsgCreateFinalityProvider`, a Babylon node will execute as follows: parameters and at most 100%. 3. Ensure the finality provider does not exist already. 4. Ensure the finality provider is not slashed. -5. Ensure the finality provider is registered at an epoch that has been BTC-timestamped. -6. Ensure the committed master public randomness is in the correct format. -7. Create a `FinalityProvider` object and save it to finality provider storage. +5. Create a `FinalityProvider` object and save it to finality provider storage. ### MsgEditFinalityProvider @@ -703,6 +704,9 @@ Upon `BeginBlock`, the BTC Staking module will execute the following: voting power table at the last height with all events that affect voting power distribution (including newly active BTC delegations, newly unbonded BTC delegations, and slashed finality providers). + Note that the voting power is assigned to a finality provider if it (1) has + BTC-timestamped public randomness, and (2) it is ranked at top `N` by the + total delegated value. 3. If the BTC Staking protocol is activated, i.e., there exists at least 1 active BTC delegation, then record the reward distribution w.r.t. the active finality providers and active BTC delegations. diff --git a/x/btcstaking/genesis_test.go b/x/btcstaking/genesis_test.go index dc034bdf..2ab7d793 100644 --- a/x/btcstaking/genesis_test.go +++ b/x/btcstaking/genesis_test.go @@ -3,11 +3,12 @@ package btcstaking_test import ( "testing" + "github.com/stretchr/testify/require" + keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/testutil/nullify" "github.com/babylonlabs-io/babylon/x/btcstaking" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/stretchr/testify/require" ) func TestGenesis(t *testing.T) { diff --git a/x/btcstaking/keeper/bench_test.go b/x/btcstaking/keeper/bench_test.go index 27fe36b6..3a41a8e1 100644 --- a/x/btcstaking/keeper/bench_test.go +++ b/x/btcstaking/keeper/bench_test.go @@ -8,11 +8,12 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + "github.com/babylonlabs-io/babylon/testutil/datagen" btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" bsmodule "github.com/babylonlabs-io/babylon/x/btcstaking" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/golang/mock/gomock" ) func benchBeginBlock(b *testing.B, numFPs int, numDelsUnderFP int) { @@ -23,8 +24,8 @@ func benchBeginBlock(b *testing.B, numFPs int, numDelsUnderFP int) { defer ctrl.Finish() btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(b, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(b, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) diff --git a/x/btcstaking/keeper/btc_height_index_test.go b/x/btcstaking/keeper/btc_height_index_test.go index a40b5d4d..a099df26 100644 --- a/x/btcstaking/keeper/btc_height_index_test.go +++ b/x/btcstaking/keeper/btc_height_index_test.go @@ -4,12 +4,13 @@ import ( "math/rand" "testing" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "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" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func FuzzBTCHeightIndex(f *testing.F) { diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index c55b9e9f..8239462a 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -175,8 +175,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, ckptKeeper) + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, nil) // covenant and slashing addr covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) @@ -381,8 +380,7 @@ func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 10}).AnyTimes() btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, ckptKeeper) + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, nil) // covenant and slashing addr covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) @@ -502,8 +500,7 @@ func FuzzFinalityProviderDelegations(f *testing.F) { btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, ckptKeeper) + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper, nil) // covenant and slashing addr covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index ea7d699d..3ebfa9af 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -23,8 +23,9 @@ func FuzzRecordVotingPowerDistCache(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes() + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -43,7 +44,8 @@ func FuzzRecordVotingPowerDistCache(f *testing.F) { } } - // for the first numFpsWithVotingPower finality providers, generate a random number of BTC delegations and add covenant signatures to activate them + // for the first numFpsWithVotingPower finality providers, generate a random number of BTC + // delegations and add covenant signatures to activate them numBTCDels := datagen.RandomInt(r, 10) + 1 stakingValue := datagen.RandomInt(r, 100000) + 100000 for _, fp := range fpsWithVotingPowerMap { @@ -71,8 +73,7 @@ func FuzzRecordVotingPowerDistCache(f *testing.F) { require.NoError(t, err) require.NotNil(t, dc) require.Equal(t, dc.TotalVotingPower, numFpsWithVotingPower*numBTCDels*stakingValue) - maxNumFps := h.BTCStakingKeeper.GetParams(h.Ctx).MaxActiveFinalityProviders - activeFPs := dc.GetActiveFinalityProviderSet(maxNumFps) + activeFPs := dc.GetActiveFinalityProviderSet() for _, fpDistInfo := range activeFPs { require.Equal(t, fpDistInfo.TotalVotingPower, numBTCDels*stakingValue) fp, ok := fpsWithVotingPowerMap[fpDistInfo.Addr] diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 5a992423..e1d38689 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -19,9 +19,9 @@ type ( cdc codec.BinaryCodec storeService corestoretypes.KVStoreService - btclcKeeper types.BTCLightClientKeeper - btccKeeper types.BtcCheckpointKeeper - ckptKeeper types.CheckpointingKeeper + btclcKeeper types.BTCLightClientKeeper + btccKeeper types.BtcCheckpointKeeper + FinalityKeeper types.FinalityKeeper hooks types.BtcStakingHooks @@ -38,7 +38,7 @@ func NewKeeper( btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper, - ckptKeeper types.CheckpointingKeeper, + finalityKeeper types.FinalityKeeper, btcNet *chaincfg.Params, authority string, @@ -47,9 +47,9 @@ func NewKeeper( cdc: cdc, storeService: storeService, - btclcKeeper: btclcKeeper, - btccKeeper: btccKeeper, - ckptKeeper: ckptKeeper, + btclcKeeper: btclcKeeper, + btccKeeper: btccKeeper, + FinalityKeeper: finalityKeeper, hooks: nil, @@ -86,7 +86,3 @@ func (k Keeper) BeginBlocker(ctx context.Context) error { return nil } - -func (k Keeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { - return k.ckptKeeper.GetLastFinalizedEpoch(ctx) -} diff --git a/x/btcstaking/keeper/keeper_test.go b/x/btcstaking/keeper/keeper_test.go index 39bb17bc..4da5c4ed 100644 --- a/x/btcstaking/keeper/keeper_test.go +++ b/x/btcstaking/keeper/keeper_test.go @@ -34,14 +34,19 @@ type Helper struct { BTCStakingKeeper *keeper.Keeper BTCLightClientKeeper *types.MockBTCLightClientKeeper BTCCheckpointKeeper *types.MockBtcCheckpointKeeper - CheckpointingKeeper *types.MockCheckpointingKeeper + FinalityKeeper *types.MockFinalityKeeper BTCStakingHooks *types.MockBtcStakingHooks MsgServer types.MsgServer Net *chaincfg.Params } -func NewHelper(t testing.TB, btclcKeeper *types.MockBTCLightClientKeeper, btccKeeper *types.MockBtcCheckpointKeeper, ckptKeeper *types.MockCheckpointingKeeper) *Helper { - k, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper, ckptKeeper) +func NewHelper( + t testing.TB, + btclcKeeper *types.MockBTCLightClientKeeper, + btccKeeper *types.MockBtcCheckpointKeeper, + finalityKeeper *types.MockFinalityKeeper, +) *Helper { + k, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper, finalityKeeper) ctx = ctx.WithHeaderInfo(header.Info{Height: 1}) msgSrvr := keeper.NewMsgServerImpl(*k) @@ -56,7 +61,6 @@ func NewHelper(t testing.TB, btclcKeeper *types.MockBTCLightClientKeeper, btccKe BTCStakingKeeper: k, BTCLightClientKeeper: btclcKeeper, BTCCheckpointKeeper: btccKeeper, - CheckpointingKeeper: ckptKeeper, MsgServer: msgSrvr, Net: &chaincfg.SimNetParams, } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index fb4a43a8..e44e4eca 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -10,9 +10,6 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - "github.com/babylonlabs-io/babylon/btcstaking" - bbn "github.com/babylonlabs-io/babylon/types" - "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/cosmos/cosmos-sdk/telemetry" @@ -20,6 +17,10 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/babylonlabs-io/babylon/btcstaking" + bbn "github.com/babylonlabs-io/babylon/types" + "github.com/babylonlabs-io/babylon/x/btcstaking/types" ) type msgServer struct { diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 431dfdea..4583b300 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -38,8 +38,8 @@ func FuzzMsgCreateFinalityProvider(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters h.GenAndApplyParams(r) @@ -145,8 +145,8 @@ func FuzzCreateBTCDelegation(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters h.GenAndApplyParams(r) @@ -190,8 +190,8 @@ func TestProperVersionInDelegation(t *testing.T) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters h.GenAndApplyParams(r) @@ -258,8 +258,8 @@ func FuzzAddCovenantSigs(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -323,8 +323,8 @@ func FuzzBTCUndelegate(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -396,8 +396,8 @@ func FuzzSelectiveSlashing(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -463,8 +463,8 @@ func FuzzSelectiveSlashing_StakingTx(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -540,8 +540,8 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set covenant PK to params _, covenantPKs := h.GenAndApplyParams(r) @@ -708,8 +708,8 @@ func TestCorrectUnbondingTimeInDelegation(t *testing.T) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters _, _ = h.GenAndApplyCustomParams(r, tt.finalizationTimeout, tt.minUnbondingTime) diff --git a/x/btcstaking/keeper/params_test.go b/x/btcstaking/keeper/params_test.go index 3ba6246a..81f4d53a 100644 --- a/x/btcstaking/keeper/params_test.go +++ b/x/btcstaking/keeper/params_test.go @@ -5,10 +5,11 @@ import ( "math/rand" "testing" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/stretchr/testify/require" ) func TestGetParams(t *testing.T) { diff --git a/x/btcstaking/keeper/power_dist_change.go b/x/btcstaking/keeper/power_dist_change.go index bde774ce..d924dd5e 100644 --- a/x/btcstaking/keeper/power_dist_change.go +++ b/x/btcstaking/keeper/power_dist_change.go @@ -35,8 +35,23 @@ func (k Keeper) UpdatePowerDist(ctx context.Context) { if len(events) == 0 { if dc != nil { // map everything in prev height to this height - k.recordVotingPowerAndCache(ctx, dc, maxActiveFps) + // NOTE: deep copy the previous dist cache because the + // cache for the new height shares the same distribution + // info due to no new events but timestamping status + // might be changed in the new dist cache after calling + // k.recordVotingPowerAndCache() + newDc := types.NewVotingPowerDistCache() + newDc.TotalVotingPower = dc.TotalVotingPower + newDc.NumActiveFps = dc.NumActiveFps + newFps := make([]*types.FinalityProviderDistInfo, len(dc.FinalityProviders)) + for i, prevFp := range dc.FinalityProviders { + newFp := *prevFp + newFps[i] = &newFp + } + newDc.FinalityProviders = newFps + k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps) } + return } @@ -56,37 +71,61 @@ func (k Keeper) UpdatePowerDist(ctx context.Context) { // to construct the new distribution newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events, maxActiveFps) - // find newly bonded finality providers and execute the hooks - newBondedFinalityProviders := newDc.FindNewActiveFinalityProviders(dc, maxActiveFps) - for _, fp := range newBondedFinalityProviders { - if err := k.hooks.AfterFinalityProviderActivated(ctx, fp.BtcPk); err != nil { - panic(fmt.Errorf("failed to execute after finality provider %s bonded", fp.BtcPk.MarshalHex())) - } - } - // record voting power and cache for this height - k.recordVotingPowerAndCache(ctx, newDc, maxActiveFps) + k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps) // record metrics - k.recordMetrics(newDc, maxActiveFps) + k.recordMetrics(newDc) } -func (k Keeper) recordVotingPowerAndCache(ctx context.Context, dc *types.VotingPowerDistCache, maxActiveFps uint32) { +// recordVotingPowerAndCache assigns voting power to each active finality provider +// with the following consideration: +// 1. the fp must have timestamped pub rand +// 2. the fp must in the top x ranked by the voting power (x is given by maxActiveFps) +// NOTE: the previous and the new dist cache cannot be nil +func (k Keeper) recordVotingPowerAndCache(ctx context.Context, prevDc, newDc *types.VotingPowerDistCache, maxActiveFps uint32) { + if prevDc == nil || newDc == nil { + panic("the voting power distribution cache cannot be nil") + } + babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) - // set voting power table for this height - for i := uint32(0); i < dc.GetNumActiveFPs(maxActiveFps); i++ { - fp := dc.FinalityProviders[i] + // label fps with whether it has timestamped pub rand so that these fps + // will not be assigned voting power + for _, fp := range newDc.FinalityProviders { + // TODO calling HasTimestampedPubRand potentially iterates + // all the pub rand committed by the fp, which might slow down + // the process, need optimization + fp.IsTimestamped = k.FinalityKeeper.HasTimestampedPubRand(ctx, fp.BtcPk, babylonTipHeight) + } + + // apply the finality provider voting power dist info to the new cache + // after which the cache would have active fps that are top N fps ranked + // by voting power with timestamped pub rand + newDc.ApplyActiveFinalityProviders(maxActiveFps) + + // set voting power table for each active finality providers at this height + for i := uint32(0); i < newDc.NumActiveFps; i++ { + fp := newDc.FinalityProviders[i] k.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonTipHeight, fp.TotalVotingPower) + } + // find newly activated finality providers and execute the hooks by comparing + // the previous dist cache + newActivatedFinalityProviders := newDc.FindNewActiveFinalityProviders(prevDc) + for _, fp := range newActivatedFinalityProviders { + if err := k.hooks.AfterFinalityProviderActivated(ctx, fp.BtcPk); err != nil { + panic(fmt.Errorf("failed to execute after finality provider %s activated", fp.BtcPk.MarshalHex())) + } + k.Logger(sdk.UnwrapSDKContext(ctx)).Info("a new finality provider is activated", "pk", fp.BtcPk.MarshalHex()) } // set the voting power distribution cache of the current height - k.setVotingPowerDistCache(ctx, babylonTipHeight, dc) + k.setVotingPowerDistCache(ctx, babylonTipHeight, newDc) } -func (k Keeper) recordMetrics(dc *types.VotingPowerDistCache, maxActiveFps uint32) { +func (k Keeper) recordMetrics(dc *types.VotingPowerDistCache) { // number of active FPs - numActiveFPs := int(dc.GetNumActiveFPs(maxActiveFps)) + numActiveFPs := int(dc.NumActiveFps) types.RecordActiveFinalityProviders(numActiveFPs) // number of inactive FPs numInactiveFPs := len(dc.FinalityProviders) - numActiveFPs @@ -236,10 +275,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( } } - // filter out the top N finality providers and their total voting power, and - // record them in the new cache - newDc.ApplyActiveFinalityProviders(maxActiveFps) - return newDc } diff --git a/x/btcstaking/keeper/power_dist_change_test.go b/x/btcstaking/keeper/power_dist_change_test.go index 65320c5d..0277ad28 100644 --- a/x/btcstaking/keeper/power_dist_change_test.go +++ b/x/btcstaking/keeper/power_dist_change_test.go @@ -4,12 +4,13 @@ import ( "math/rand" "testing" - "github.com/babylonlabs-io/babylon/testutil/datagen" - btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + + "github.com/babylonlabs-io/babylon/testutil/datagen" + btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" + "github.com/babylonlabs-io/babylon/x/btcstaking/types" ) func FuzzProcessAllPowerDistUpdateEvents_Determinism(f *testing.F) { @@ -23,8 +24,9 @@ func FuzzProcessAllPowerDistUpdateEvents_Determinism(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes() + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters h.GenAndApplyParams(r) @@ -75,8 +77,9 @@ func FuzzFinalityProviderEvents(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes() + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -153,8 +156,8 @@ func FuzzBTCDelegationEvents(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -217,10 +220,21 @@ func FuzzBTCDelegationEvents(f *testing.F) { require.Equal(t, expectedStakingTxHash, btcDelStateUpdate.StakingTxHash) require.Equal(t, types.BTCDelegationStatus_ACTIVE, btcDelStateUpdate.NewState) - // ensure this finality provider has voting power at the current height + // ensure this finality provider does not have voting power at the current height + // due to no timestamped randomness + babylonHeight += 1 + h.SetCtxHeight(babylonHeight) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(btcTip).AnyTimes() + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Eq(babylonHeight)).Return(false).AnyTimes() + err = h.BTCStakingKeeper.BeginBlocker(h.Ctx) + h.NoError(err) + require.Zero(t, h.BTCStakingKeeper.GetVotingPower(h.Ctx, *fp.BtcPk, babylonHeight)) + + // ensure this finality provider has voting power at the current height after having timestamped pub rand babylonHeight += 1 h.SetCtxHeight(babylonHeight) h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Eq(h.Ctx)).Return(btcTip).AnyTimes() + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Eq(babylonHeight)).Return(true).AnyTimes() err = h.BTCStakingKeeper.BeginBlocker(h.Ctx) h.NoError(err) require.Equal(t, uint64(stakingValue), h.BTCStakingKeeper.GetVotingPower(h.Ctx, *fp.BtcPk, babylonHeight)) diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go index e3817697..9522e06c 100644 --- a/x/btcstaking/keeper/query_params_test.go +++ b/x/btcstaking/keeper/query_params_test.go @@ -3,9 +3,10 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" + testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/stretchr/testify/require" ) func TestParamsQuery(t *testing.T) { diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index a8c743af..a76ee2e8 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -5,11 +5,12 @@ import ( "sort" "testing" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func FuzzVotingPowerTable(f *testing.F) { @@ -23,8 +24,9 @@ func FuzzVotingPowerTable(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes() + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -162,8 +164,8 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) @@ -173,6 +175,7 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { // generate a random batch of finality providers, each with a BTC delegation with random power fpsWithMeta := []*types.FinalityProviderDistInfo{} numFps := datagen.RandomInt(r, 300) + 1 + noTimestampedFps := map[string]bool{} for i := uint64(0); i < numFps; i++ { // generate finality provider _, _, fp := h.CreateFinalityProvider(r) @@ -188,17 +191,26 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { ) h.CreateCovenantSigs(r, covenantSKs, delMsg, del) + // 30 percent not have timestamped randomness, which causes + // zero voting power in the table + fpDistInfo := &types.FinalityProviderDistInfo{BtcPk: fp.BtcPk, TotalVotingPower: stakingValue} + if r.Intn(10) <= 2 { + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), fp.BtcPk, gomock.Any()).Return(false).AnyTimes() + noTimestampedFps[fp.BtcPk.MarshalHex()] = true + fpDistInfo.IsTimestamped = false + } else { + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), fp.BtcPk, gomock.Any()).Return(true).AnyTimes() + fpDistInfo.IsTimestamped = true + } + // record voting power - fpsWithMeta = append(fpsWithMeta, &types.FinalityProviderDistInfo{ - BtcPk: fp.BtcPk, - TotalVotingPower: stakingValue, - }) + fpsWithMeta = append(fpsWithMeta, fpDistInfo) } maxActiveFpsParam := h.BTCStakingKeeper.GetParams(h.Ctx).MaxActiveFinalityProviders // get a map of expected active finality providers - types.SortFinalityProviders(fpsWithMeta) - expectedActiveFps := fpsWithMeta[:min(uint32(len(fpsWithMeta)), maxActiveFpsParam)] + types.SortFinalityProvidersWithTimestamping(fpsWithMeta) + expectedActiveFps := fpsWithMeta[:min(uint32(len(fpsWithMeta)-len(noTimestampedFps)), maxActiveFpsParam)] expectedActiveFpsMap := map[string]uint64{} for _, fp := range expectedActiveFps { expectedActiveFpsMap[fp.BtcPk.MarshalHex()] = fp.TotalVotingPower @@ -211,7 +223,7 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { err = h.BTCStakingKeeper.BeginBlocker(h.Ctx) require.NoError(t, err) - // only finality providers in expectedActiveFpsMap have voting power + // only finality providers in expectedActiveFpsMap have voting power for _, fp := range fpsWithMeta { power := h.BTCStakingKeeper.GetVotingPower(h.Ctx, fp.BtcPk.MustMarshal(), babylonHeight) if expectedPower, ok := expectedActiveFpsMap[fp.BtcPk.MarshalHex()]; ok { @@ -247,8 +259,9 @@ func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) + finalityKeeper := types.NewMockFinalityKeeper(ctrl) + finalityKeeper.EXPECT().HasTimestampedPubRand(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes() + h := NewHelper(t, btclcKeeper, btccKeeper, finalityKeeper) // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7c4b6abf..b7338a84 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -42,10 +42,18 @@ func (fp *FinalityProvider) ValidateBasic() error { return nil } -// SortFinalityProviders sorts the finality providers slice, +// SortFinalityProvidersWithTimestamping sorts the finality providers slice, // from higher to lower voting power -func SortFinalityProviders(fps []*FinalityProviderDistInfo) { +// finality providers that are timestamped come higher than +// those are not +func SortFinalityProvidersWithTimestamping(fps []*FinalityProviderDistInfo) { sort.SliceStable(fps, func(i, j int) bool { + if fps[i].IsTimestamped && !fps[j].IsTimestamped { + return true + } + if !fps[i].IsTimestamped && fps[j].IsTimestamped { + return false + } return fps[i].TotalVotingPower > fps[j].TotalVotingPower }) } diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 5de1421d..ffca7d9a 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -11,7 +11,6 @@ var ( ErrBTCDelegationNotFound = errorsmod.Register(ModuleName, 1102, "the BTC delegation is not found") ErrFpRegistered = errorsmod.Register(ModuleName, 1103, "the finality provider has already been registered") ErrFpAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the finality provider has already been slashed") - ErrFpNotBTCTimestamped = errorsmod.Register(ModuleName, 1105, "the finality provider is not BTC timestamped yet") ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1106, "the BTC staking protocol is not activated yet") ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1107, "the BTC height is not found") ErrReusedStakingTx = errorsmod.Register(ModuleName, 1108, "the BTC staking tx is already used") diff --git a/x/btcstaking/types/expected_keepers.go b/x/btcstaking/types/expected_keepers.go index 519162b9..3f4a1262 100644 --- a/x/btcstaking/types/expected_keepers.go +++ b/x/btcstaking/types/expected_keepers.go @@ -6,7 +6,6 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - etypes "github.com/babylonlabs-io/babylon/x/epoching/types" ) type BTCLightClientKeeper interface { @@ -19,9 +18,8 @@ type BtcCheckpointKeeper interface { GetParams(ctx context.Context) (p btcctypes.Params) } -type CheckpointingKeeper interface { - GetEpoch(ctx context.Context) *etypes.Epoch - GetLastFinalizedEpoch(ctx context.Context) uint64 +type FinalityKeeper interface { + HasTimestampedPubRand(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, height uint64) bool } type BtcStakingHooks interface { diff --git a/x/btcstaking/types/incentive.go b/x/btcstaking/types/incentive.go index 7dd02089..e6955ee2 100644 --- a/x/btcstaking/types/incentive.go +++ b/x/btcstaking/types/incentive.go @@ -20,9 +20,9 @@ func (dc *VotingPowerDistCache) AddFinalityProviderDistInfo(v *FinalityProviderD dc.FinalityProviders = append(dc.FinalityProviders, v) } -func (dc *VotingPowerDistCache) FindNewActiveFinalityProviders(prevDc *VotingPowerDistCache, maxActiveFPs uint32) []*FinalityProviderDistInfo { - activeFps := dc.GetActiveFinalityProviderSet(maxActiveFPs) - prevActiveFps := prevDc.GetActiveFinalityProviderSet(maxActiveFPs) +func (dc *VotingPowerDistCache) FindNewActiveFinalityProviders(prevDc *VotingPowerDistCache) []*FinalityProviderDistInfo { + activeFps := dc.GetActiveFinalityProviderSet() + prevActiveFps := prevDc.GetActiveFinalityProviderSet() newActiveFps := make([]*FinalityProviderDistInfo, 0) for pk, fp := range activeFps { @@ -36,28 +36,44 @@ func (dc *VotingPowerDistCache) FindNewActiveFinalityProviders(prevDc *VotingPow } // ApplyActiveFinalityProviders sorts all finality providers, counts the total voting -// power of top N finality providers, and records them in cache +// power of top N finality providers, excluding those who don't have timestamped pub rand +// and records them in cache func (dc *VotingPowerDistCache) ApplyActiveFinalityProviders(maxActiveFPs uint32) { - // reset total voting power - dc.TotalVotingPower = 0 - // sort finality providers - SortFinalityProviders(dc.FinalityProviders) - // calculate voting power of top N finality providers - numActiveFPs := dc.GetNumActiveFPs(maxActiveFPs) + // sort finality providers with timestamping considered + SortFinalityProvidersWithTimestamping(dc.FinalityProviders) + + numActiveFPs := uint32(0) + + // finality providers are in the descending order of voting power + // and timestamped ones come in the last + for _, fp := range dc.FinalityProviders { + if numActiveFPs == maxActiveFPs { + break + } + if fp.TotalVotingPower == 0 { + break + } + if !fp.IsTimestamped { + break + } + numActiveFPs++ + } + + totalVotingPower := uint64(0) + for i := uint32(0); i < numActiveFPs; i++ { - dc.TotalVotingPower += dc.FinalityProviders[i].TotalVotingPower + totalVotingPower += dc.FinalityProviders[i].TotalVotingPower } -} -func (dc *VotingPowerDistCache) GetNumActiveFPs(maxActiveFPs uint32) uint32 { - return min(maxActiveFPs, uint32(len(dc.FinalityProviders))) + dc.TotalVotingPower = totalVotingPower + dc.NumActiveFps = numActiveFPs } // GetActiveFinalityProviderSet returns a set of active finality providers // keyed by the hex string of the finality provider's BTC public key // i.e., top N of them in terms of voting power -func (dc *VotingPowerDistCache) GetActiveFinalityProviderSet(maxActiveFPs uint32) map[string]*FinalityProviderDistInfo { - numActiveFPs := dc.GetNumActiveFPs(maxActiveFPs) +func (dc *VotingPowerDistCache) GetActiveFinalityProviderSet() map[string]*FinalityProviderDistInfo { + numActiveFPs := dc.NumActiveFps activeFps := make(map[string]*FinalityProviderDistInfo) @@ -71,8 +87,8 @@ func (dc *VotingPowerDistCache) GetActiveFinalityProviderSet(maxActiveFPs uint32 // FilterVotedDistCache filters out a voting power distribution cache // with finality providers that have voted according to a map of given // voters, and their total voted power. -func (dc *VotingPowerDistCache) FilterVotedDistCache(maxActiveFPs uint32, voterBTCPKs map[string]struct{}) *VotingPowerDistCache { - activeFPs := dc.GetActiveFinalityProviderSet(maxActiveFPs) +func (dc *VotingPowerDistCache) FilterVotedDistCache(voterBTCPKs map[string]struct{}) *VotingPowerDistCache { + activeFPs := dc.GetActiveFinalityProviderSet() var filteredFps []*FinalityProviderDistInfo totalVotingPower := uint64(0) for k, v := range activeFPs { diff --git a/x/btcstaking/types/incentive.pb.go b/x/btcstaking/types/incentive.pb.go index e1234e07..e3bfc77f 100644 --- a/x/btcstaking/types/incentive.pb.go +++ b/x/btcstaking/types/incentive.pb.go @@ -29,9 +29,14 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // VotingPowerDistCache is the cache for voting power distribution of finality providers // and their BTC delegations at a height type VotingPowerDistCache struct { + // total_voting_power is the total voting power of all the finality providers + // in the cache TotalVotingPower uint64 `protobuf:"varint,1,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` // finality_providers is a list of finality providers' voting power information FinalityProviders []*FinalityProviderDistInfo `protobuf:"bytes,2,rep,name=finality_providers,json=finalityProviders,proto3" json:"finality_providers,omitempty"` + // num_active_fps is the number of finality providers that have active BTC + // delegations as well as timestamped public randomness + NumActiveFps uint32 `protobuf:"varint,3,opt,name=num_active_fps,json=numActiveFps,proto3" json:"num_active_fps,omitempty"` } func (m *VotingPowerDistCache) Reset() { *m = VotingPowerDistCache{} } @@ -67,20 +72,6 @@ func (m *VotingPowerDistCache) XXX_DiscardUnknown() { var xxx_messageInfo_VotingPowerDistCache proto.InternalMessageInfo -func (m *VotingPowerDistCache) GetTotalVotingPower() uint64 { - if m != nil { - return m.TotalVotingPower - } - return 0 -} - -func (m *VotingPowerDistCache) GetFinalityProviders() []*FinalityProviderDistInfo { - if m != nil { - return m.FinalityProviders - } - return nil -} - // FinalityProviderDistInfo is the reward distribution of a finality provider and its BTC delegations type FinalityProviderDistInfo struct { // btc_pk is the Bitcoin secp256k1 PK of this finality provider @@ -94,6 +85,9 @@ type FinalityProviderDistInfo struct { TotalVotingPower uint64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,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 + IsTimestamped bool `protobuf:"varint,6,opt,name=is_timestamped,json=isTimestamped,proto3" json:"is_timestamped,omitempty"` } func (m *FinalityProviderDistInfo) Reset() { *m = FinalityProviderDistInfo{} } @@ -150,6 +144,13 @@ func (m *FinalityProviderDistInfo) GetBtcDels() []*BTCDelDistInfo { return nil } +func (m *FinalityProviderDistInfo) GetIsTimestamped() bool { + if m != nil { + return m.IsTimestamped + } + return false +} + // BTCDelDistInfo contains the information related to reward distribution for a BTC delegation type BTCDelDistInfo struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation @@ -228,39 +229,43 @@ func init() { } var fileDescriptor_ac354c3bd6d7a66b = []byte{ - // 504 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0x9b, 0xae, 0x1b, 0xe0, 0x8e, 0x7f, 0x56, 0x91, 0xc2, 0x90, 0xb2, 0x52, 0x69, 0xa8, - 0x87, 0x35, 0x61, 0x6c, 0x07, 0xb8, 0x41, 0x16, 0x21, 0x26, 0xfe, 0x45, 0x61, 0xe2, 0xc0, 0x81, - 0xc8, 0x76, 0xdc, 0xc4, 0x6a, 0x12, 0x57, 0xb1, 0x17, 0x9a, 0x6f, 0xc1, 0x87, 0xe0, 0x23, 0xec, - 0x43, 0x70, 0x9c, 0x76, 0x9a, 0x76, 0x98, 0x50, 0x7b, 0xe0, 0x6b, 0xa0, 0x38, 0x81, 0x15, 0xb4, - 0x4a, 0x3d, 0x70, 0xb3, 0xfd, 0x3c, 0xaf, 0xdf, 0xf7, 0xf9, 0x49, 0x2f, 0xd8, 0xc2, 0x08, 0x17, - 0x31, 0x4f, 0x2d, 0x2c, 0x89, 0x90, 0x68, 0xc4, 0xd2, 0xd0, 0xca, 0x77, 0x2c, 0x96, 0x12, 0x9a, - 0x4a, 0x96, 0x53, 0x73, 0x9c, 0x71, 0xc9, 0xe1, 0xbd, 0xda, 0x66, 0x5e, 0xda, 0xcc, 0x7c, 0x67, - 0xa3, 0x13, 0xf2, 0x90, 0x2b, 0x87, 0x55, 0x9e, 0x2a, 0xf3, 0xc6, 0x7d, 0xc2, 0x45, 0xc2, 0x85, - 0x5f, 0x09, 0xd5, 0xa5, 0x92, 0x7a, 0xdf, 0x34, 0xd0, 0xf9, 0xc8, 0x25, 0x4b, 0x43, 0x97, 0x7f, - 0xa1, 0x99, 0xc3, 0x84, 0xdc, 0x47, 0x24, 0xa2, 0x70, 0x1b, 0x40, 0xc9, 0x25, 0x8a, 0xfd, 0x5c, - 0xa9, 0xfe, 0xb8, 0x94, 0x75, 0xad, 0xab, 0xf5, 0x5b, 0xde, 0x1d, 0xa5, 0xcc, 0x95, 0xc1, 0xcf, - 0x00, 0x0e, 0x59, 0x8a, 0x62, 0x26, 0x8b, 0xb2, 0x4b, 0xce, 0x02, 0x9a, 0x09, 0xbd, 0xd9, 0x5d, - 0xe9, 0xb7, 0x9f, 0x58, 0xe6, 0x95, 0xb3, 0x9a, 0x2f, 0xeb, 0x02, 0xb7, 0xf6, 0x97, 0xbd, 0x0f, - 0xd2, 0x21, 0xf7, 0xee, 0x0e, 0xff, 0x51, 0x44, 0xef, 0xac, 0x09, 0xf4, 0x45, 0x7e, 0xf8, 0x1e, - 0xac, 0x61, 0x49, 0xfc, 0xf1, 0x48, 0x8d, 0xb7, 0x6e, 0x3f, 0x3d, 0xbf, 0xd8, 0xdc, 0x0b, 0x99, - 0x8c, 0x8e, 0xb0, 0x49, 0x78, 0x62, 0xd5, 0xed, 0x63, 0x84, 0xc5, 0x80, 0xf1, 0xdf, 0x57, 0x4b, - 0x16, 0x63, 0x2a, 0x4c, 0xfb, 0xc0, 0xdd, 0xdd, 0x7b, 0xec, 0x1e, 0xe1, 0xd7, 0xb4, 0xf0, 0x56, - 0xb1, 0x24, 0xee, 0x08, 0x6e, 0x83, 0x16, 0x0a, 0x82, 0x4c, 0x6f, 0x76, 0xb5, 0xfe, 0x0d, 0x5b, - 0x3f, 0x3d, 0x1e, 0x74, 0x6a, 0x68, 0x2f, 0x82, 0x20, 0xa3, 0x42, 0x7c, 0x90, 0x19, 0x4b, 0x43, - 0x4f, 0xb9, 0xe0, 0x5b, 0x00, 0x08, 0x4f, 0x12, 0x26, 0x04, 0xe3, 0xa9, 0xbe, 0xa2, 0x6a, 0x06, - 0xe7, 0x17, 0x9b, 0x0f, 0xaa, 0x1a, 0x11, 0x8c, 0x4c, 0xc6, 0xad, 0x04, 0xc9, 0xc8, 0x7c, 0x43, - 0x43, 0x44, 0x0a, 0x87, 0x92, 0xd3, 0xe3, 0x01, 0xa8, 0xbf, 0x74, 0x28, 0xf1, 0xe6, 0x3e, 0x58, - 0x00, 0xbe, 0xb5, 0x00, 0xfc, 0x73, 0x70, 0xbd, 0xcc, 0x1e, 0xd0, 0x58, 0xe8, 0xab, 0x0a, 0xf7, - 0xd6, 0x02, 0xdc, 0xf6, 0xe1, 0xbe, 0x43, 0xe3, 0x3f, 0x90, 0xaf, 0x61, 0x49, 0x1c, 0x1a, 0x8b, - 0xde, 0x4f, 0x0d, 0xdc, 0xfa, 0x5b, 0xfb, 0xff, 0x40, 0x9f, 0x81, 0x76, 0x39, 0x09, 0xcd, 0xfc, - 0xa5, 0xb8, 0x82, 0xca, 0x5c, 0x3e, 0xc2, 0x47, 0xe0, 0x76, 0x1d, 0xc2, 0x97, 0x13, 0x3f, 0x42, - 0x22, 0xaa, 0x10, 0x7b, 0x37, 0xeb, 0xe7, 0xc3, 0xc9, 0x2b, 0x24, 0x22, 0xf8, 0x10, 0xac, 0x5f, - 0x01, 0xac, 0x9d, 0x5f, 0xb2, 0xb2, 0xdf, 0x7d, 0x9f, 0x1a, 0xda, 0xc9, 0xd4, 0xd0, 0x7e, 0x4c, - 0x0d, 0xed, 0xeb, 0xcc, 0x68, 0x9c, 0xcc, 0x8c, 0xc6, 0xd9, 0xcc, 0x68, 0x7c, 0x5a, 0x22, 0xdc, - 0x64, 0x7e, 0x21, 0x55, 0x52, 0xbc, 0xa6, 0x56, 0x68, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x84, 0x61, 0x20, 0xde, 0xb3, 0x03, 0x00, 0x00, + // 565 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0xdb, 0x34, 0x5f, 0x3b, 0xf9, 0xf9, 0x60, 0x14, 0x24, 0x53, 0x24, 0x27, 0x44, 0x04, + 0x65, 0xd1, 0xd8, 0x94, 0x76, 0x01, 0xac, 0xa8, 0x1b, 0x55, 0x54, 0xfc, 0x45, 0x26, 0x62, 0xc1, + 0x02, 0x6b, 0x3c, 0x9e, 0xd8, 0xa3, 0xd8, 0x1e, 0xcb, 0x33, 0x31, 0xc9, 0x1b, 0xb0, 0xe4, 0x11, + 0x78, 0x88, 0x3e, 0x04, 0xcb, 0xa8, 0x2b, 0xd4, 0x45, 0x85, 0x92, 0x05, 0x8f, 0x01, 0xf2, 0x0f, + 0x24, 0xa0, 0x46, 0xea, 0x82, 0xdd, 0xcc, 0x3d, 0xe7, 0xde, 0xb9, 0xe7, 0x1c, 0x0d, 0x68, 0x5b, + 0xc8, 0x9a, 0x7a, 0x2c, 0xd0, 0x2c, 0x81, 0xb9, 0x40, 0x23, 0x1a, 0x38, 0x5a, 0xbc, 0xaf, 0xd1, + 0x00, 0x93, 0x40, 0xd0, 0x98, 0xa8, 0x61, 0xc4, 0x04, 0x83, 0xb7, 0x72, 0x9a, 0xba, 0xa4, 0xa9, + 0xf1, 0xfe, 0x6e, 0xdd, 0x61, 0x0e, 0x4b, 0x19, 0x5a, 0x72, 0xca, 0xc8, 0xbb, 0xb7, 0x31, 0xe3, + 0x3e, 0xe3, 0x66, 0x06, 0x64, 0x97, 0x0c, 0x6a, 0xcd, 0x24, 0x50, 0x7f, 0xcb, 0x04, 0x0d, 0x9c, + 0x3e, 0xfb, 0x40, 0xa2, 0x1e, 0xe5, 0xe2, 0x18, 0x61, 0x97, 0xc0, 0x3d, 0x00, 0x05, 0x13, 0xc8, + 0x33, 0xe3, 0x14, 0x35, 0xc3, 0x04, 0x96, 0xa5, 0xa6, 0xd4, 0x29, 0x1a, 0x37, 0x52, 0x64, 0xa5, + 0x0d, 0xbe, 0x07, 0x70, 0x48, 0x03, 0xe4, 0x51, 0x31, 0x4d, 0x5e, 0x89, 0xa9, 0x4d, 0x22, 0x2e, + 0x6f, 0x34, 0x37, 0x3b, 0xe5, 0x87, 0x9a, 0x7a, 0xe5, 0xae, 0xea, 0x49, 0xde, 0xd0, 0xcf, 0xf9, + 0xc9, 0xdb, 0xa7, 0xc1, 0x90, 0x19, 0x37, 0x87, 0x7f, 0x21, 0x1c, 0xde, 0x03, 0xb5, 0x60, 0xec, + 0x9b, 0x08, 0x27, 0x16, 0x98, 0xc3, 0x90, 0xcb, 0x9b, 0x4d, 0xa9, 0x53, 0x35, 0x2a, 0xc1, 0xd8, + 0x3f, 0x4a, 0x8b, 0x27, 0x21, 0x7f, 0x52, 0xfc, 0xf8, 0xb9, 0x51, 0x68, 0xfd, 0xd8, 0x00, 0xf2, + 0xba, 0xd9, 0xf0, 0x35, 0x28, 0x59, 0x02, 0x9b, 0xe1, 0x28, 0x95, 0x52, 0xd1, 0x1f, 0x5d, 0x5c, + 0x36, 0x0e, 0x1d, 0x2a, 0xdc, 0xb1, 0xa5, 0x62, 0xe6, 0x6b, 0xf9, 0xaa, 0x1e, 0xb2, 0x78, 0x97, + 0xb2, 0x5f, 0x57, 0x4d, 0x4c, 0x43, 0xc2, 0x55, 0xfd, 0xb4, 0x7f, 0x70, 0xf8, 0xa0, 0x3f, 0xb6, + 0x9e, 0x93, 0xa9, 0xb1, 0x65, 0x09, 0xdc, 0x1f, 0xc1, 0x3d, 0x50, 0x44, 0xb6, 0x1d, 0xc9, 0x1b, + 0x4d, 0xa9, 0xb3, 0xa3, 0xcb, 0xe7, 0x67, 0xdd, 0x7a, 0x6e, 0xf0, 0x91, 0x6d, 0x47, 0x84, 0xf3, + 0x37, 0x22, 0xa2, 0x81, 0x63, 0xa4, 0x2c, 0xf8, 0x12, 0x00, 0xcc, 0x7c, 0x9f, 0x72, 0x4e, 0x59, + 0x90, 0x6a, 0xd8, 0xd1, 0xbb, 0x17, 0x97, 0x8d, 0x3b, 0x59, 0x0f, 0xb7, 0x47, 0x2a, 0x65, 0x9a, + 0x8f, 0x84, 0xab, 0xbe, 0x20, 0x0e, 0xc2, 0xd3, 0x1e, 0xc1, 0xe7, 0x67, 0x5d, 0x90, 0x8f, 0xec, + 0x11, 0x6c, 0xac, 0x0c, 0x58, 0x13, 0x52, 0x71, 0x4d, 0x48, 0x4f, 0xc1, 0x76, 0xa2, 0xdd, 0x26, + 0x1e, 0x97, 0xb7, 0xd2, 0x68, 0xda, 0x6b, 0xa2, 0xd1, 0x07, 0xc7, 0x3d, 0xe2, 0xfd, 0x0e, 0xe4, + 0x3f, 0x4b, 0xe0, 0x1e, 0xf1, 0x38, 0x6c, 0x83, 0x1a, 0xe5, 0xa6, 0xa0, 0x3e, 0xe1, 0x02, 0xf9, + 0x21, 0xb1, 0xe5, 0x52, 0x53, 0xea, 0x6c, 0x1b, 0x55, 0xca, 0x07, 0xcb, 0x62, 0xeb, 0xbb, 0x04, + 0x6a, 0x7f, 0x8e, 0xf8, 0xf7, 0xbe, 0x3f, 0x06, 0xe5, 0x64, 0x61, 0x12, 0x99, 0xd7, 0xb2, 0x1f, + 0x64, 0xe4, 0xa4, 0x08, 0xef, 0x83, 0xff, 0x73, 0xad, 0xa6, 0x98, 0x98, 0x2e, 0xe2, 0x6e, 0x96, + 0x84, 0x51, 0xcd, 0xcb, 0x83, 0xc9, 0x33, 0xc4, 0x5d, 0x78, 0x17, 0x54, 0xae, 0xf0, 0xb5, 0x1c, + 0x2f, 0x2d, 0xd5, 0x5f, 0x7d, 0x99, 0x2b, 0xd2, 0x6c, 0xae, 0x48, 0xdf, 0xe6, 0x8a, 0xf4, 0x69, + 0xa1, 0x14, 0x66, 0x0b, 0xa5, 0xf0, 0x75, 0xa1, 0x14, 0xde, 0x5d, 0x43, 0xdc, 0x64, 0xf5, 0x8f, + 0xa7, 0x4a, 0xad, 0x52, 0xfa, 0x2b, 0x0f, 0x7e, 0x06, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x88, 0x9d, + 0x74, 0x06, 0x04, 0x00, 0x00, } func (m *VotingPowerDistCache) Marshal() (dAtA []byte, err error) { @@ -283,6 +288,11 @@ func (m *VotingPowerDistCache) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.NumActiveFps != 0 { + i = encodeVarintIncentive(dAtA, i, uint64(m.NumActiveFps)) + i-- + dAtA[i] = 0x18 + } if len(m.FinalityProviders) > 0 { for iNdEx := len(m.FinalityProviders) - 1; iNdEx >= 0; iNdEx-- { { @@ -325,6 +335,16 @@ func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l + if m.IsTimestamped { + i-- + if m.IsTimestamped { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } if len(m.BtcDels) > 0 { for iNdEx := len(m.BtcDels) - 1; iNdEx >= 0; iNdEx-- { { @@ -458,6 +478,9 @@ func (m *VotingPowerDistCache) Size() (n int) { n += 1 + l + sovIncentive(uint64(l)) } } + if m.NumActiveFps != 0 { + n += 1 + sovIncentive(uint64(m.NumActiveFps)) + } return n } @@ -488,6 +511,9 @@ func (m *FinalityProviderDistInfo) Size() (n int) { n += 1 + l + sovIncentive(uint64(l)) } } + if m.IsTimestamped { + n += 2 + } return n } @@ -603,6 +629,25 @@ func (m *VotingPowerDistCache) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NumActiveFps", wireType) + } + m.NumActiveFps = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NumActiveFps |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipIncentive(dAtA[iNdEx:]) @@ -809,6 +854,26 @@ func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsTimestamped", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsTimestamped = bool(v != 0) default: iNdEx = preIndex skippy, err := skipIncentive(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/incentive_test.go b/x/btcstaking/types/incentive_test.go new file mode 100644 index 00000000..fe844fe2 --- /dev/null +++ b/x/btcstaking/types/incentive_test.go @@ -0,0 +1,119 @@ +package types + +import ( + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" + + "github.com/babylonlabs-io/babylon/types" +) + +var ( + fpPrivKey1, _ = btcec.NewPrivateKey() + fpPrivKey2, _ = btcec.NewPrivateKey() + fpPubKey1 = types.NewBIP340PubKeyFromBTCPK(fpPrivKey1.PubKey()) + fpPubKey2 = types.NewBIP340PubKeyFromBTCPK(fpPrivKey2.PubKey()) +) + +func TestVotingPowerDistCache(t *testing.T) { + tests := []struct { + desc string + maxActiveFPs uint32 + numActiveFps uint32 + totalVotingPower uint64 + prevDistCache *VotingPowerDistCache + fps []*FinalityProviderDistInfo + }{ + { + desc: "all not timestamped", + maxActiveFPs: 80, + numActiveFps: 0, + totalVotingPower: 0, + prevDistCache: NewVotingPowerDistCache(), + fps: []*FinalityProviderDistInfo{ + { + BtcPk: fpPubKey1, + TotalVotingPower: 1000, + IsTimestamped: false, + }, + { + BtcPk: fpPubKey2, + TotalVotingPower: 2000, + IsTimestamped: false, + }, + }, + }, + { + desc: "all timestamped", + maxActiveFPs: 80, + numActiveFps: 2, + totalVotingPower: 3000, + prevDistCache: NewVotingPowerDistCache(), + fps: []*FinalityProviderDistInfo{ + { + BtcPk: fpPubKey1, + TotalVotingPower: 1000, + IsTimestamped: true, + }, + { + BtcPk: fpPubKey2, + TotalVotingPower: 2000, + IsTimestamped: true, + }, + }, + }, + { + desc: "partly timestamped", + maxActiveFPs: 80, + numActiveFps: 1, + totalVotingPower: 1000, + prevDistCache: NewVotingPowerDistCache(), + fps: []*FinalityProviderDistInfo{ + { + BtcPk: fpPubKey1, + TotalVotingPower: 1000, + IsTimestamped: true, + }, + { + BtcPk: fpPubKey2, + TotalVotingPower: 2000, + IsTimestamped: false, + }, + }, + }, + { + desc: "small max active fps", + maxActiveFPs: 1, + numActiveFps: 1, + totalVotingPower: 2000, + prevDistCache: NewVotingPowerDistCache(), + fps: []*FinalityProviderDistInfo{ + { + BtcPk: fpPubKey1, + TotalVotingPower: 1000, + IsTimestamped: true, + }, + { + BtcPk: fpPubKey2, + TotalVotingPower: 2000, + IsTimestamped: true, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + dc := NewVotingPowerDistCache() + for _, fp := range tc.fps { + dc.AddFinalityProviderDistInfo(fp) + } + dc.ApplyActiveFinalityProviders(tc.maxActiveFPs) + require.Equal(t, tc.totalVotingPower, dc.TotalVotingPower) + require.Equal(t, tc.numActiveFps, dc.NumActiveFps) + + newBondedFps := dc.FindNewActiveFinalityProviders(tc.prevDistCache) + require.Equal(t, tc.numActiveFps, uint32(len(newBondedFps))) + }) + } +} diff --git a/x/btcstaking/types/mocked_keepers.go b/x/btcstaking/types/mocked_keepers.go index 41c4b0a9..2b7cfc3e 100644 --- a/x/btcstaking/types/mocked_keepers.go +++ b/x/btcstaking/types/mocked_keepers.go @@ -11,7 +11,6 @@ import ( types "github.com/babylonlabs-io/babylon/types" types0 "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" types1 "github.com/babylonlabs-io/babylon/x/btclightclient/types" - types2 "github.com/babylonlabs-io/babylon/x/epoching/types" gomock "github.com/golang/mock/gomock" ) @@ -117,55 +116,41 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetParams(ctx interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockBtcCheckpointKeeper)(nil).GetParams), ctx) } -// MockCheckpointingKeeper is a mock of CheckpointingKeeper interface. -type MockCheckpointingKeeper struct { +// MockFinalityKeeper is a mock of FinalityKeeper interface. +type MockFinalityKeeper struct { ctrl *gomock.Controller - recorder *MockCheckpointingKeeperMockRecorder + recorder *MockFinalityKeeperMockRecorder } -// MockCheckpointingKeeperMockRecorder is the mock recorder for MockCheckpointingKeeper. -type MockCheckpointingKeeperMockRecorder struct { - mock *MockCheckpointingKeeper +// MockFinalityKeeperMockRecorder is the mock recorder for MockFinalityKeeper. +type MockFinalityKeeperMockRecorder struct { + mock *MockFinalityKeeper } -// NewMockCheckpointingKeeper creates a new mock instance. -func NewMockCheckpointingKeeper(ctrl *gomock.Controller) *MockCheckpointingKeeper { - mock := &MockCheckpointingKeeper{ctrl: ctrl} - mock.recorder = &MockCheckpointingKeeperMockRecorder{mock} +// NewMockFinalityKeeper creates a new mock instance. +func NewMockFinalityKeeper(ctrl *gomock.Controller) *MockFinalityKeeper { + mock := &MockFinalityKeeper{ctrl: ctrl} + mock.recorder = &MockFinalityKeeperMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockCheckpointingKeeper) EXPECT() *MockCheckpointingKeeperMockRecorder { +func (m *MockFinalityKeeper) EXPECT() *MockFinalityKeeperMockRecorder { return m.recorder } -// GetEpoch mocks base method. -func (m *MockCheckpointingKeeper) GetEpoch(ctx context.Context) *types2.Epoch { +// HasTimestampedPubRand mocks base method. +func (m *MockFinalityKeeper) HasTimestampedPubRand(ctx context.Context, fpBtcPK *types.BIP340PubKey, height uint64) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetEpoch", ctx) - ret0, _ := ret[0].(*types2.Epoch) + ret := m.ctrl.Call(m, "HasTimestampedPubRand", ctx, fpBtcPK, height) + ret0, _ := ret[0].(bool) return ret0 } -// GetEpoch indicates an expected call of GetEpoch. -func (mr *MockCheckpointingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call { +// HasTimestampedPubRand indicates an expected call of HasTimestampedPubRand. +func (mr *MockFinalityKeeperMockRecorder) HasTimestampedPubRand(ctx, fpBtcPK, height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpoch", reflect.TypeOf((*MockCheckpointingKeeper)(nil).GetEpoch), ctx) -} - -// GetLastFinalizedEpoch mocks base method. -func (m *MockCheckpointingKeeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLastFinalizedEpoch", ctx) - ret0, _ := ret[0].(uint64) - return ret0 -} - -// GetLastFinalizedEpoch indicates an expected call of GetLastFinalizedEpoch. -func (mr *MockCheckpointingKeeperMockRecorder) GetLastFinalizedEpoch(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastFinalizedEpoch", reflect.TypeOf((*MockCheckpointingKeeper)(nil).GetLastFinalizedEpoch), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasTimestampedPubRand", reflect.TypeOf((*MockFinalityKeeper)(nil).HasTimestampedPubRand), ctx, fpBtcPK, height) } // MockBtcStakingHooks is a mock of BtcStakingHooks interface. diff --git a/x/finality/README.md b/x/finality/README.md index a6aa1739..11c35a76 100644 --- a/x/finality/README.md +++ b/x/finality/README.md @@ -9,6 +9,8 @@ The Finality module is responsible for handling finality votes, maintaining the finalization status of blocks, and identifying equivocating finality providers in the finalization rounds. This includes: +- handling requests for committing EOTS public randomness from finality +providers; - handling requests for submitting finality votes from finality providers; - maintaining the finalization status of blocks; - identifying sluggish finality providers; and @@ -19,10 +21,13 @@ in the finalization rounds. This includes: - [Table of contents](#table-of-contents) - [Concepts](#concepts) - [States](#states) + - [Parameters](#parameters) + - [Public randomness](#public-randomness) - [Finality votes](#finality-votes) - [Indexed blocks with finalization status](#indexed-blocks-with-finalization-status) - [Equivocation evidences](#equivocation-evidences) - [Messages](#messages) + - [MsgCommitPubRandList](#msgcommitpubrandlist) - [MsgAddFinalitySig](#msgaddfinalitysig) - [MsgUpdateParams](#msgupdateparams) - [EndBlocker](#endblocker) @@ -77,17 +82,14 @@ participate in the finality voting round, an active finality provider with BTC delegations (as specified in the [BTC Staking module](../btcstaking/README.md)) needs to interact with Babylon as follows: -- **Committing EOTS master public randomness.** The finality provider needs to - generate a pair of EOTS master secret/public randomness, and commit the master - public randomness when registering itself to Babylon. The EOTS master - secret/public randomness allows to derive a EOTS secret/public randomness - deterministically for each given height, respectively. Babylon further - requires the epoch of the finality provider registration to be finalized by - BTC timestamping before the registered finality provider can submit finality - signatures. This ensures that each finality provider has a unique public - randomness for each height, and that if the finality provider submits two - finality signatures over two conflicting blocks, anyone can extract the - finality provider's secret key using EOTS. +- **Committing EOTS public randomness.** The finality provider proactively + sends a merkle-tree-based commit of a list of *EOTS public randomness* + for future heights to the Finality module. EOTS ensures that given an EOTS + public randomness, a signer can only sign a single message. + Otherwise, anyone can extract the signer's secret key + by using two EOTS signatures on different messages, the corresponding EOTS + public randomness, and the signer's public key. + A public randomness commit takes effect only after it is BTC-timestamped. - **Submitting EOTS signatures.** Upon a new block, the finality provider submits an EOTS signature w.r.t. the derived public randomness at that height. The Finality module will verify the EOTS signature, and check if there are @@ -106,6 +108,61 @@ transactions to the Bitcoin network. The Finality module maintains the following KV stores. +### Parameters + +The [parameter storage](./keeper/params.go) maintains the Finality module's +parameters. The Finality module's parameters are represented as a `Params` +[object](../../proto/babylon/finality/v1/params.proto) defined as follows: + +```protobuf +// Params defines the parameters for the module. +message Params { + option (gogoproto.goproto_stringer) = false; + // signed_blocks_window defines the size of the sliding window for tracking finality provider liveness + int64 signed_blocks_window = 1; + // finality_sig_timeout defines how much time (in terms of blocks) finality providers have to cast a finality + // vote before being judged as missing their voting turn on the given block + int64 finality_sig_timeout = 2; + // min_signed_per_window defines the minimum number of blocks that a finality provider is required to sign + // within the sliding window to avoid being detected as sluggish + bytes min_signed_per_window = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; + // min_pub_rand is the minimum number of public randomness each + // message should commit + uint64 min_pub_rand = 4; +} +``` + +### Public randomness + +The [public randomness storage](./keeper/public_randomness.go) maintains the +EOTS public randomness commit that each finality provider commits to Babylon. +The key is the finality provider's Bitcoin secp256k1 public key concatenated +with the block height, and the value is a merkle tree constructed by the list +of public randomness with starting height, and the number of public randomness. +It also stores the epoch number at which Babylon receives the commit. + +```protobuf +// PubRandCommit is a commitment to a series of public randomness +// currently, the commitment is a root of a Merkle tree that includes +// a series of public randomness +message PubRandCommit { + // start_height is the height of the first commitment + uint64 start_height = 1; + // num_pub_rand is the number of committed public randomness + uint64 num_pub_rand = 2; + // commitment is the value of the commitment + // currently, it is the root of the merkle tree constructed by the public randomness + bytes commitment = 3; + // epoch_num defines the epoch number that the commit falls into + uint64 epoch_num = 4; +} +``` + ### Finality votes The [finality vote storage](./keeper/votes.go) maintains the finality votes of @@ -240,6 +297,46 @@ message formats are defined at The message handlers are defined at [x/finality/keeper/msg_server.go](./keeper/msg_server.go). +### MsgCommitPubRandList + +The `MsgCommitPubRandList` message is used for committing a merkle tree +constructed by a list of EOTS public randomness that will be used by a +finality provider in the future. It is typically submitted by a finality +provider via the [finality provider](https://github.com/babylonchain/finality-provider) program. + +```protobuf +// MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS +message MsgCommitPubRandList { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; + // fp_btc_pk is the BTC PK of the finality provider that commits the public randomness + bytes fp_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ]; + // start_height is the start block height of the list of public randomness + uint64 start_height = 3; + // num_pub_rand is the number of public randomness committed + uint64 num_pub_rand = 4; + // commitment is the commitment of these public randomness + // currently it's the root of the Merkle tree that includes these public randomness + bytes commitment = 5; + // sig is the signature on (start_height || num_pub_rand || commitment) signed by + // SK corresponding to fp_btc_pk. This prevents others to commit public + // randomness on behalf of fp_btc_pk + bytes sig = 6 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340Signature" ]; +} +``` + +Upon `MsgCommitPubRandList`, a Babylon node will execute as follows: + +1. Ensure the message contains at least `MinPubRand` number of EOTS public + randomness, where `MinPubRand` is defined in the module parameters. +2. Ensure the finality provider has been registered in Babylon. +3. Ensure the list of EOTS public randomness does not overlap with existing EOTS + public randomness that this finality provider previously committed before. +4. Verify the Schnorr signature over the list of public randomness signed by the + finality provider. +5. Store the list of EOTS public randomness along with the current epoch number + to the public randomness storage. + ### MsgAddFinalitySig The `MsgAddFinalitySig` message is used for submitting a finality vote, i.e., an diff --git a/x/finality/genesis_test.go b/x/finality/genesis_test.go index f66eb355..3e7b821f 100644 --- a/x/finality/genesis_test.go +++ b/x/finality/genesis_test.go @@ -3,11 +3,12 @@ package finality_test import ( "testing" + "github.com/stretchr/testify/require" + keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/testutil/nullify" "github.com/babylonlabs-io/babylon/x/finality" "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/stretchr/testify/require" ) func TestGenesis(t *testing.T) { @@ -15,7 +16,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx := keepertest.FinalityKeeper(t, nil, nil) + k, ctx := keepertest.FinalityKeeper(t, nil, nil, nil) finality.InitGenesis(ctx, *k, genesisState) got := finality.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/finality/keeper/genesis_test.go b/x/finality/keeper/genesis_test.go index cdad2911..695b504d 100644 --- a/x/finality/keeper/genesis_test.go +++ b/x/finality/keeper/genesis_test.go @@ -16,7 +16,7 @@ func FuzzTestExportGenesis(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - k, ctx := keepertest.FinalityKeeper(t, nil, nil) + k, ctx := keepertest.FinalityKeeper(t, nil, nil, nil) btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index d8d87869..8f15e20a 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -14,6 +14,7 @@ import ( "github.com/babylonlabs-io/babylon/testutil/datagen" testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/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" ) @@ -24,7 +25,7 @@ func FuzzBlock(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) height := datagen.RandomInt(r, 100) @@ -55,7 +56,7 @@ func FuzzListBlocks(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // index a random list of finalised blocks @@ -146,7 +147,7 @@ func FuzzVotesAtHeight(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // Add random number of voted finality providers to the store @@ -191,7 +192,8 @@ func FuzzListPubRandCommit(f *testing.F) { // Setup keeper and context bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := testkeeper.FinalityKeeper(t, bsKeeper, nil) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := testkeeper.FinalityKeeper(t, bsKeeper, nil, cKeeper) ctx = sdk.UnwrapSDKContext(ctx) ms := keeper.NewMsgServerImpl(*fKeeper) @@ -205,6 +207,7 @@ func FuzzListPubRandCommit(f *testing.F) { require.NoError(t, err) bsKeeper.EXPECT().GetFinalityProvider(gomock.Any(), gomock.Eq(bip340PK.MustMarshal())).Return(fp, nil).AnyTimes() bsKeeper.EXPECT().HasFinalityProvider(gomock.Any(), gomock.Eq(bip340PK.MustMarshal())).Return(true).AnyTimes() + cKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: 1}).AnyTimes() numPrCommitList := datagen.RandomInt(r, 10) + 1 prCommitList := []*types.PubRandCommit{} @@ -260,7 +263,7 @@ func FuzzQueryEvidence(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // set random BTC SK PK @@ -307,7 +310,7 @@ func FuzzListEvidences(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // generate a random list of evidences since startHeight @@ -371,7 +374,7 @@ func FuzzSigningInfo(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - fKeeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + fKeeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // generate a random list of signing info diff --git a/x/finality/keeper/keeper.go b/x/finality/keeper/keeper.go index fe185a56..d569896f 100644 --- a/x/finality/keeper/keeper.go +++ b/x/finality/keeper/keeper.go @@ -18,8 +18,9 @@ type ( cdc codec.BinaryCodec storeService corestoretypes.KVStoreService - BTCStakingKeeper types.BTCStakingKeeper - IncentiveKeeper types.IncentiveKeeper + BTCStakingKeeper types.BTCStakingKeeper + IncentiveKeeper types.IncentiveKeeper + CheckpointingKeeper types.CheckpointingKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -38,6 +39,7 @@ func NewKeeper( storeService corestoretypes.KVStoreService, btcstakingKeeper types.BTCStakingKeeper, incentiveKeeper types.IncentiveKeeper, + checkpointingKeeper types.CheckpointingKeeper, authority string, ) Keeper { sb := collections.NewSchemaBuilder(storeService) @@ -45,9 +47,10 @@ func NewKeeper( cdc: cdc, storeService: storeService, - BTCStakingKeeper: btcstakingKeeper, - IncentiveKeeper: incentiveKeeper, - authority: authority, + BTCStakingKeeper: btcstakingKeeper, + IncentiveKeeper: incentiveKeeper, + CheckpointingKeeper: checkpointingKeeper, + authority: authority, FinalityProviderSigningTracker: collections.NewMap( sb, types.FinalityProviderSigningInfoKeyPrefix, @@ -81,5 +84,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { } func (k Keeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { - return k.BTCStakingKeeper.GetLastFinalizedEpoch(ctx) + return k.CheckpointingKeeper.GetLastFinalizedEpoch(ctx) +} + +func (k Keeper) GetCurrentEpoch(ctx context.Context) uint64 { + currentEpoch := k.CheckpointingKeeper.GetEpoch(ctx) + if currentEpoch == nil { + panic("cannot get the current epoch") + } + + return currentEpoch.EpochNumber } diff --git a/x/finality/keeper/liveness.go b/x/finality/keeper/liveness.go index 59cf453c..f46e52b3 100644 --- a/x/finality/keeper/liveness.go +++ b/x/finality/keeper/liveness.go @@ -142,7 +142,7 @@ func (k Keeper) updateSigningInfo( // fetch signing info signInfo, err := k.FinalityProviderSigningTracker.Get(ctx, fpPk.MustMarshal()) if err != nil { - return false, nil, err + return false, nil, fmt.Errorf("the signing info is not created") } signedBlocksWindow := params.SignedBlocksWindow diff --git a/x/finality/keeper/liveness_test.go b/x/finality/keeper/liveness_test.go index da92702e..74f25f2d 100644 --- a/x/finality/keeper/liveness_test.go +++ b/x/finality/keeper/liveness_test.go @@ -24,7 +24,8 @@ func FuzzHandleLiveness(f *testing.F) { bsKeeper := types.NewMockBTCStakingKeeper(ctrl) bsKeeper.EXPECT().GetParams(gomock.Any()).Return(bstypes.Params{MaxActiveFinalityProviders: 100}).AnyTimes() iKeeper := types.NewMockIncentiveKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper, cKeeper) mockedHooks := types.NewMockFinalityHooks(ctrl) mockedHooks.EXPECT().AfterSluggishFinalityProviderDetected(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 3576f7cd..7fdb6172 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -7,12 +7,13 @@ import ( "time" errorsmod "cosmossdk.io/errors" - bbn "github.com/babylonlabs-io/babylon/types" - bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" - "github.com/babylonlabs-io/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + bbn "github.com/babylonlabs-io/babylon/types" + bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + "github.com/babylonlabs-io/babylon/x/finality/types" ) type msgServer struct { @@ -82,7 +83,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal } fpPK := req.FpBtcPk if ms.BTCStakingKeeper.GetVotingPower(ctx, fpPK.MustMarshal(), req.BlockHeight) == 0 { - return nil, types.ErrInvalidFinalitySig.Wrapf("the finality provider %v does not have voting power at height %d", fpPK.MustMarshal(), req.BlockHeight) + return nil, types.ErrInvalidFinalitySig.Wrapf("the finality provider %s does not have voting power at height %d", fpPK.MarshalHex(), req.BlockHeight) } // ensure the finality provider has not cast the same vote yet @@ -96,8 +97,8 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal return &types.MsgAddFinalitySigResponse{}, nil } - // find the public randomness commitment for this height from this finality provider - prCommit, err := ms.GetPubRandCommitForHeight(ctx, req.FpBtcPk, req.BlockHeight) + // find the timestamped public randomness commitment for this height from this finality provider + prCommit, err := ms.GetTimestampedPubRandCommitForHeight(ctx, req.FpBtcPk, req.BlockHeight) if err != nil { return nil, err } @@ -132,7 +133,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // if this finality provider has also signed canonical block, slash it canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, fpPK) if err == nil { - //set canonial sig + // set canonial sig evidence.CanonicalFinalitySig = canonicalSig // slash this finality provider, including setting its voting power to // zero, extracting its BTC SK, and emit an event @@ -206,6 +207,7 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi StartHeight: req.StartHeight, NumPubRand: req.NumPubRand, Commitment: req.Commitment, + EpochNum: ms.GetCurrentEpoch(ctx), } // get last public randomness commitment diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 231ef895..7bb39d9c 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -7,18 +7,20 @@ import ( "time" "cosmossdk.io/core/header" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/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" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { - fKeeper, ctx := keepertest.FinalityKeeper(t, nil, nil) + fKeeper, ctx := keepertest.FinalityKeeper(t, nil, nil, nil) return fKeeper, keeper.NewMsgServerImpl(*fKeeper), ctx } @@ -29,7 +31,7 @@ func TestMsgServer(t *testing.T) { } func FuzzCommitPubRandList(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) + datagen.AddRandomSeedsToFuzzer(f, 100) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -37,8 +39,11 @@ func FuzzCommitPubRandList(f *testing.F) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil, cKeeper) ms := keeper.NewMsgServerImpl(*fKeeper) + committedEpochNum := datagen.GenRandomEpochNum(r) + cKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: committedEpochNum}).AnyTimes() // create a random finality provider btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) @@ -75,6 +80,11 @@ func FuzzCommitPubRandList(f *testing.F) { // query last public randomness and assert lastPrCommit := fKeeper.GetLastPubRandCommit(ctx, fpBTCPK) require.NotNil(t, lastPrCommit) + require.Equal(t, committedEpochNum, lastPrCommit.EpochNum) + committedHeight := datagen.RandomInt(r, int(numPubRand)) + startHeight + commitByHeight, err := fKeeper.GetPubRandCommitForHeight(ctx, fpBTCPK, committedHeight) + require.NoError(t, err) + require.Equal(t, committedEpochNum, commitByHeight.EpochNum) // Case 4: commit a pubrand list with overlap of the existing pubrand in KVStore and it should fail overlappedStartHeight := startHeight + numPubRand - 1 - datagen.RandomInt(r, 5) @@ -93,7 +103,7 @@ func FuzzCommitPubRandList(f *testing.F) { } func FuzzAddFinalitySig(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) + datagen.AddRandomSeedsToFuzzer(f, 100) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -101,7 +111,8 @@ func FuzzAddFinalitySig(f *testing.F) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil, cKeeper) ms := keeper.NewMsgServerImpl(*fKeeper) // create and register a random finality provider @@ -113,6 +124,11 @@ func FuzzAddFinalitySig(f *testing.F) { fpBTCPKBytes := fpBTCPK.MustMarshal() require.NoError(t, err) bsKeeper.EXPECT().HasFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(true).AnyTimes() + + // set committed epoch num + committedEpochNum := datagen.GenRandomEpochNum(r) + 1 + cKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: committedEpochNum}).AnyTimes() + // commit some public randomness startHeight := uint64(0) numPubRand := uint64(200) @@ -128,6 +144,18 @@ func FuzzAddFinalitySig(f *testing.F) { msg, err := datagen.NewMsgAddFinalitySig(signer, btcSK, startHeight, blockHeight, randListInfo, blockAppHash) require.NoError(t, err) + // Case 0: fail if the committed epoch is not finalized + lastFinalizedEpoch := datagen.RandomInt(r, int(committedEpochNum)) + o1 := cKeeper.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(lastFinalizedEpoch).Times(1) + bsKeeper.EXPECT().GetVotingPower(gomock.Any(), gomock.Eq(fpBTCPKBytes), gomock.Eq(blockHeight)).Return(uint64(1)).Times(1) + bsKeeper.EXPECT().GetFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(fp, nil).Times(1) + _, err = ms.AddFinalitySig(ctx, msg) + require.ErrorIs(t, err, types.ErrPubRandCommitNotBTCTimestamped) + + // set the committed epoch finalized for the rest of the cases + lastFinalizedEpoch = datagen.GenRandomEpochNum(r) + committedEpochNum + cKeeper.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(lastFinalizedEpoch).After(o1).AnyTimes() + // Case 1: fail if the finality provider does not have voting power bsKeeper.EXPECT().GetVotingPower(gomock.Any(), gomock.Eq(fpBTCPKBytes), gomock.Eq(blockHeight)).Return(uint64(0)).Times(1) bsKeeper.EXPECT().GetFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(fp, nil).Times(1) @@ -208,7 +236,8 @@ func TestVoteForConflictingHashShouldRetrieveEvidenceAndSlash(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil, cKeeper) ms := keeper.NewMsgServerImpl(*fKeeper) // create and register a random finality provider btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) @@ -220,6 +249,8 @@ func TestVoteForConflictingHashShouldRetrieveEvidenceAndSlash(t *testing.T) { require.NoError(t, err) bsKeeper.EXPECT().HasFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(true).AnyTimes() + cKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: 1}).AnyTimes() + cKeeper.EXPECT().GetLastFinalizedEpoch(gomock.Any()).Return(uint64(1)).AnyTimes() // commit some public randomness startHeight := uint64(0) numPubRand := uint64(200) @@ -245,7 +276,6 @@ func TestVoteForConflictingHashShouldRetrieveEvidenceAndSlash(t *testing.T) { // some "evidence" ctx = ctx.WithHeaderInfo(header.Info{Height: int64(blockHeight), AppHash: forkHash}) msg1, err := datagen.NewMsgAddFinalitySig(signer, btcSK, startHeight, blockHeight, randListInfo, forkHash) - require.NoError(t, err) bsKeeper.EXPECT().GetVotingPower(gomock.Any(), gomock.Eq(fpBTCPKBytes), diff --git a/x/finality/keeper/params_test.go b/x/finality/keeper/params_test.go index 649fd3ed..b4621546 100644 --- a/x/finality/keeper/params_test.go +++ b/x/finality/keeper/params_test.go @@ -3,13 +3,14 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" + testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/stretchr/testify/require" ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.FinalityKeeper(t, nil, nil) + k, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) params := types.DefaultParams() err := k.SetParams(ctx, params) diff --git a/x/finality/keeper/public_randomness.go b/x/finality/keeper/public_randomness.go index 68cb87e8..d68dc6f4 100644 --- a/x/finality/keeper/public_randomness.go +++ b/x/finality/keeper/public_randomness.go @@ -7,9 +7,10 @@ import ( "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + bbn "github.com/babylonlabs-io/babylon/types" "github.com/babylonlabs-io/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) /* @@ -33,6 +34,33 @@ func (k Keeper) GetPubRandCommitForHeight(ctx context.Context, fpBtcPK *bbn.BIP3 return nil, types.ErrPubRandNotFound } +// GetTimestampedPubRandCommitForHeight finds the public randomness commitment that includes the given +// height for the given finality provider +func (k Keeper) GetTimestampedPubRandCommitForHeight(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, height uint64) (*types.PubRandCommit, error) { + prCommit, err := k.GetPubRandCommitForHeight(ctx, fpBtcPK, height) + if err != nil { + return nil, err + } + + // ensure the finality provider's last randomness commit is already finalised by BTC timestamping + finalizedEpoch := k.GetLastFinalizedEpoch(ctx) + if finalizedEpoch == 0 { + return nil, types.ErrPubRandCommitNotBTCTimestamped.Wrapf("no finalized epoch yet") + } + if finalizedEpoch < prCommit.EpochNum { + return nil, types.ErrPubRandCommitNotBTCTimestamped. + Wrapf("the finality provider %s last committed epoch number: %d, last finalized epoch number: %d", + fpBtcPK.MarshalHex(), prCommit.EpochNum, finalizedEpoch) + } + + return prCommit, nil +} + +func (k Keeper) HasTimestampedPubRand(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, height uint64) bool { + _, err := k.GetTimestampedPubRandCommitForHeight(ctx, fpBtcPK, height) + return err == nil +} + // SetPubRandCommit adds the given public randomness commitment for the given public key func (k Keeper) SetPubRandCommit(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, prCommit *types.PubRandCommit) { store := k.pubRandCommitFpStore(ctx, fpBtcPK) diff --git a/x/finality/keeper/query_params_test.go b/x/finality/keeper/query_params_test.go index 2bdbfcc0..bc670b3e 100644 --- a/x/finality/keeper/query_params_test.go +++ b/x/finality/keeper/query_params_test.go @@ -3,13 +3,14 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" + testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper" "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/stretchr/testify/require" ) func TestParamsQuery(t *testing.T) { - keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil, nil) params := types.DefaultParams() err := keeper.SetParams(ctx, params) require.NoError(t, err) diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index 9af4c6fe..efc2ef4e 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/babylonlabs-io/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonlabs-io/babylon/x/finality/types" ) // TallyBlocks tries to finalise all blocks that are non-finalised AND have a non-nil @@ -89,8 +90,7 @@ func (k Keeper) finalizeBlock(ctx context.Context, block *types.IndexedBlock, vo panic(err) } // filter out voted finality providers - maxActiveFPs := k.BTCStakingKeeper.GetParams(ctx).MaxActiveFinalityProviders - filteredDc := dc.FilterVotedDistCache(maxActiveFPs, voterBTCPKs) + filteredDc := dc.FilterVotedDistCache(voterBTCPKs) // reward voted finality providers k.IncentiveKeeper.RewardBTCStaking(ctx, block.Height, filteredDc) // remove reward distribution cache afterwards diff --git a/x/finality/keeper/tallying_bench_test.go b/x/finality/keeper/tallying_bench_test.go index abe4c38c..52b1532d 100644 --- a/x/finality/keeper/tallying_bench_test.go +++ b/x/finality/keeper/tallying_bench_test.go @@ -8,13 +8,14 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func benchmarkTallyBlocks(b *testing.B, numFPs int) { @@ -24,7 +25,8 @@ func benchmarkTallyBlocks(b *testing.B, numFPs int) { bsKeeper := types.NewMockBTCStakingKeeper(ctrl) iKeeper := types.NewMockIncentiveKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(b, bsKeeper, iKeeper) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(b, bsKeeper, iKeeper, cKeeper) // activate BTC staking protocol at a random height activatedHeight := uint64(1) diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 88198513..9a69988d 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -5,15 +5,16 @@ import ( "math/rand" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/babylonlabs-io/babylon/x/finality/keeper" "github.com/babylonlabs-io/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func FuzzTallying_PanicCases(f *testing.F) { @@ -26,7 +27,8 @@ func FuzzTallying_PanicCases(f *testing.F) { bsKeeper := types.NewMockBTCStakingKeeper(ctrl) iKeeper := types.NewMockIncentiveKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper, cKeeper) // Case 1: expect to panic if tallying upon BTC staking protocol is not activated bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(0), bstypes.ErrBTCStakingNotActivated).Times(1) @@ -56,7 +58,8 @@ func FuzzTallying_FinalizingNoBlock(f *testing.F) { bsKeeper := types.NewMockBTCStakingKeeper(ctrl) iKeeper := types.NewMockIncentiveKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper, cKeeper) // activate BTC staking protocol at a random height activatedHeight := datagen.RandomInt(r, 10) + 1 @@ -99,7 +102,8 @@ func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { bsKeeper := types.NewMockBTCStakingKeeper(ctrl) bsKeeper.EXPECT().GetParams(gomock.Any()).Return(bstypes.Params{MaxActiveFinalityProviders: 100}).AnyTimes() iKeeper := types.NewMockIncentiveKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper, cKeeper) // activate BTC staking protocol at a random height activatedHeight := datagen.RandomInt(r, 10) + 1 diff --git a/x/finality/keeper/votes_bench_test.go b/x/finality/keeper/votes_bench_test.go index 2f78a75c..3a0f97fa 100644 --- a/x/finality/keeper/votes_bench_test.go +++ b/x/finality/keeper/votes_bench_test.go @@ -8,13 +8,14 @@ import ( "time" "cosmossdk.io/core/header" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" keepertest "github.com/babylonlabs-io/babylon/testutil/keeper" bbn "github.com/babylonlabs-io/babylon/types" "github.com/babylonlabs-io/babylon/x/finality/keeper" "github.com/babylonlabs-io/babylon/x/finality/types" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func benchmarkAddFinalitySig(b *testing.B) { @@ -23,7 +24,8 @@ func benchmarkAddFinalitySig(b *testing.B) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(b, bsKeeper, nil) + cKeeper := types.NewMockCheckpointingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(b, bsKeeper, nil, cKeeper) ms := keeper.NewMsgServerImpl(*fKeeper) // create a random finality provider diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index ee10b27f..12a2966d 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -6,15 +6,15 @@ import ( // x/finality module sentinel errors var ( - ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") - ErrVoteNotFound = errorsmod.Register(ModuleName, 1101, "vote is not found") - ErrHeightTooHigh = errorsmod.Register(ModuleName, 1102, "the chain has not reached the given height yet") - ErrPubRandNotFound = errorsmod.Register(ModuleName, 1103, "public randomness is not found") - ErrPubRandCommitNotFound = errorsmod.Register(ModuleName, 1104, "public randomness commitment is not found") - ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the finality provider has not committed any public randomness yet") - ErrTooFewPubRand = errorsmod.Register(ModuleName, 1106, "the request contains too few public randomness") - ErrInvalidPubRand = errorsmod.Register(ModuleName, 1107, "the public randomness list is invalid") - ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1108, "evidence is not found") - ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1109, "finality signature is not valid") - ErrNoSlashableEvidence = errorsmod.Register(ModuleName, 1110, "there is no slashable evidence") + ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") + ErrVoteNotFound = errorsmod.Register(ModuleName, 1101, "vote is not found") + ErrHeightTooHigh = errorsmod.Register(ModuleName, 1102, "the chain has not reached the given height yet") + ErrPubRandNotFound = errorsmod.Register(ModuleName, 1103, "public randomness is not found") + ErrNoPubRandYet = errorsmod.Register(ModuleName, 1104, "the finality provider has not committed any public randomness yet") + ErrTooFewPubRand = errorsmod.Register(ModuleName, 1105, "the request contains too few public randomness") + ErrInvalidPubRand = errorsmod.Register(ModuleName, 1106, "the public randomness list is invalid") + ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1107, "evidence is not found") + ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1108, "finality signature is not valid") + ErrNoSlashableEvidence = errorsmod.Register(ModuleName, 1109, "there is no slashable evidence") + ErrPubRandCommitNotBTCTimestamped = errorsmod.Register(ModuleName, 1110, "the public randomness commit is not BTC timestamped yet") ) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index f4d6e58d..f3e47d3f 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -5,6 +5,7 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + etypes "github.com/babylonlabs-io/babylon/x/epoching/types" ) type BTCStakingKeeper interface { @@ -17,10 +18,14 @@ type BTCStakingKeeper interface { GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) GetVotingPowerDistCache(ctx context.Context, height uint64) (*bstypes.VotingPowerDistCache, error) RemoveVotingPowerDistCache(ctx context.Context, height uint64) - GetLastFinalizedEpoch(ctx context.Context) uint64 RevertSluggishFinalityProvider(ctx context.Context, fpBTCPK []byte) error } +type CheckpointingKeeper interface { + GetEpoch(ctx context.Context) *etypes.Epoch + GetLastFinalizedEpoch(ctx context.Context) uint64 +} + // IncentiveKeeper defines the expected interface needed to distribute rewards. type IncentiveKeeper interface { RewardBTCStaking(ctx context.Context, height uint64, filteredDc *bstypes.VotingPowerDistCache) diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 8d2eb5f4..0b1804fb 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -100,6 +100,8 @@ type PubRandCommit struct { // commitment is the value of the commitment // currently, it is the root of the merkle tree constructed by the public randomness Commitment []byte `protobuf:"bytes,3,opt,name=commitment,proto3" json:"commitment,omitempty"` + // epoch_num defines the epoch number that the commit falls into + EpochNum uint64 `protobuf:"varint,4,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` } func (m *PubRandCommit) Reset() { *m = PubRandCommit{} } @@ -156,6 +158,13 @@ func (m *PubRandCommit) GetCommitment() []byte { return nil } +func (m *PubRandCommit) GetEpochNum() uint64 { + if m != nil { + return m.EpochNum + } + return 0 +} + // Evidence is the evidence that a finality provider has signed finality // signatures with correct public randomness on two conflicting Babylon headers type Evidence struct { @@ -304,41 +313,43 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 539 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x4d, 0x6f, 0xd3, 0x30, - 0x18, 0x6e, 0x68, 0x69, 0x3b, 0x37, 0x13, 0x90, 0x8d, 0xa9, 0x7c, 0x28, 0x2b, 0x39, 0xf5, 0x00, - 0x2d, 0xfb, 0x10, 0x42, 0xdc, 0xc8, 0x34, 0xb4, 0x0a, 0x24, 0xaa, 0x74, 0x5c, 0xb8, 0x58, 0x4e, - 0xe2, 0x24, 0x56, 0x1b, 0xdb, 0x8a, 0x9d, 0x6a, 0xe5, 0x07, 0x70, 0xe6, 0x67, 0x21, 0x4e, 0x3b, - 0xa2, 0x1d, 0x26, 0xd4, 0xfe, 0x11, 0x14, 0xe7, 0xa3, 0xeb, 0x09, 0x04, 0xda, 0x2d, 0x7e, 0xdf, - 0x57, 0xcf, 0xc7, 0xeb, 0x27, 0x06, 0x96, 0x8b, 0xdc, 0xc5, 0x8c, 0xd1, 0x61, 0x40, 0x28, 0x9a, - 0x11, 0xb9, 0x18, 0xce, 0x0f, 0xaa, 0xef, 0x01, 0x4f, 0x98, 0x64, 0xc6, 0x4e, 0x31, 0x33, 0xa8, - 0xea, 0xf3, 0x83, 0xc7, 0xbb, 0x21, 0x0b, 0x99, 0xea, 0x0f, 0xb3, 0xaf, 0x7c, 0xd4, 0x82, 0x40, - 0x1f, 0x51, 0x1f, 0x5f, 0x60, 0xdf, 0x9e, 0x31, 0x6f, 0x6a, 0xec, 0x81, 0x66, 0x84, 0x49, 0x18, - 0xc9, 0xae, 0xd6, 0xd3, 0xfa, 0x0d, 0xa7, 0x38, 0x19, 0x8f, 0x40, 0x1b, 0x71, 0x0e, 0x23, 0x24, - 0xa2, 0xee, 0x9d, 0x9e, 0xd6, 0xd7, 0x9d, 0x16, 0xe2, 0xfc, 0x0c, 0x89, 0xc8, 0x78, 0x0a, 0xb6, - 0x72, 0x9e, 0x2f, 0xd8, 0xef, 0xd6, 0x7b, 0x5a, 0xbf, 0xed, 0xac, 0x0b, 0x96, 0x04, 0xdb, 0xe3, - 0xd4, 0x75, 0x10, 0xf5, 0x4f, 0x58, 0x1c, 0x13, 0x69, 0x3c, 0x03, 0xba, 0x90, 0x28, 0x91, 0x70, - 0x83, 0xa7, 0xa3, 0x6a, 0x67, 0x39, 0x59, 0x0f, 0xe8, 0x34, 0x8d, 0x21, 0x4f, 0x5d, 0x98, 0x20, - 0xea, 0x2b, 0xc2, 0x86, 0x03, 0x68, 0x1a, 0x17, 0x50, 0x86, 0x09, 0x80, 0xa7, 0xe0, 0x62, 0x4c, - 0xa5, 0x22, 0xd5, 0x9d, 0x1b, 0x15, 0xeb, 0x6b, 0x03, 0xb4, 0x4f, 0xe7, 0xc4, 0xc7, 0xd4, 0xc3, - 0xc6, 0x39, 0xd8, 0x0a, 0x38, 0x74, 0xa5, 0x07, 0xf9, 0x54, 0xd1, 0xe9, 0xf6, 0xeb, 0xab, 0xeb, - 0xfd, 0xe3, 0x90, 0xc8, 0x28, 0x75, 0x07, 0x1e, 0x8b, 0x87, 0xc5, 0xc2, 0x66, 0xc8, 0x15, 0x2f, - 0x08, 0x2b, 0x8f, 0x43, 0xb9, 0xe0, 0x58, 0x0c, 0xec, 0xd1, 0xf8, 0xe8, 0xf8, 0xe5, 0x38, 0x75, - 0xdf, 0xe3, 0x85, 0xd3, 0x0a, 0xb8, 0x2d, 0xbd, 0xf1, 0x34, 0xf3, 0xe1, 0x66, 0x2b, 0x2b, 0x7d, - 0xe4, 0x22, 0x3b, 0xaa, 0x56, 0xf8, 0xf8, 0x04, 0xda, 0x95, 0x07, 0xa5, 0xd1, 0x7e, 0x73, 0x75, - 0xbd, 0xff, 0xea, 0x6f, 0x79, 0x27, 0x5e, 0x44, 0x59, 0x92, 0x14, 0x9e, 0x9d, 0x16, 0x2f, 0xcc, - 0x3f, 0x07, 0x86, 0x87, 0x28, 0xa3, 0xc4, 0x43, 0x33, 0x58, 0xdd, 0x4a, 0x43, 0x2d, 0xe1, 0x7e, - 0xd5, 0x79, 0x5b, 0x5c, 0x8f, 0x05, 0xb6, 0x03, 0x96, 0x4c, 0xd7, 0x83, 0x77, 0xd5, 0x60, 0x27, - 0x2b, 0x96, 0x33, 0x1c, 0xec, 0xad, 0x11, 0xcb, 0xd0, 0x40, 0x41, 0xc2, 0x6e, 0xf3, 0x9f, 0x65, - 0x9f, 0x7e, 0x3c, 0x9f, 0x4c, 0x48, 0xe8, 0xec, 0x56, 0xc8, 0xef, 0x0a, 0xe0, 0x09, 0x09, 0x8d, - 0x00, 0x3c, 0x50, 0xaa, 0x36, 0xc8, 0x5a, 0xff, 0x4d, 0x76, 0x2f, 0x03, 0xbd, 0xc1, 0x63, 0xfd, - 0xd0, 0xc0, 0x93, 0xf2, 0x3c, 0x4e, 0x58, 0x16, 0x89, 0x64, 0x42, 0x42, 0x4a, 0x68, 0x38, 0xa2, - 0x01, 0xbb, 0xbd, 0x6c, 0x6c, 0x64, 0x3c, 0xcb, 0x46, 0x7d, 0x33, 0xe3, 0x87, 0xe0, 0x61, 0x4c, - 0x84, 0xc0, 0x3e, 0x54, 0x89, 0x11, 0xd0, 0x63, 0x29, 0x95, 0x38, 0x51, 0x41, 0xa9, 0x3b, 0x3b, - 0x79, 0x53, 0xfd, 0x94, 0xe2, 0x24, 0x6f, 0xd9, 0x1f, 0xbe, 0x2f, 0x4d, 0xed, 0x72, 0x69, 0x6a, - 0xbf, 0x96, 0xa6, 0xf6, 0x6d, 0x65, 0xd6, 0x2e, 0x57, 0x66, 0xed, 0xe7, 0xca, 0xac, 0x7d, 0x3e, - 0xfc, 0xb3, 0xde, 0x8b, 0xf5, 0x8b, 0xa1, 0xa4, 0xbb, 0x4d, 0xf5, 0x02, 0x1c, 0xfd, 0x0e, 0x00, - 0x00, 0xff, 0xff, 0xb1, 0x31, 0x31, 0x81, 0x52, 0x04, 0x00, 0x00, + // 561 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0x8d, 0x49, 0xc8, 0xc7, 0xc6, 0x15, 0xe0, 0x96, 0x2a, 0x50, 0xe4, 0x06, 0x9f, 0x72, 0x80, + 0x84, 0x7e, 0x08, 0x21, 0x6e, 0xb8, 0x2a, 0x6a, 0x04, 0x82, 0xc8, 0x29, 0x17, 0x2e, 0xab, 0xf5, + 0x7a, 0x6d, 0xaf, 0x12, 0xef, 0xae, 0xec, 0x75, 0xd4, 0xf0, 0x03, 0x38, 0xc3, 0xbf, 0x42, 0x9c, + 0x7a, 0x44, 0x3d, 0x54, 0x28, 0xf9, 0x23, 0xc8, 0x6b, 0xc7, 0x69, 0x4e, 0x20, 0x10, 0x37, 0xef, + 0x9b, 0xf1, 0xbc, 0x79, 0xb3, 0x6f, 0x16, 0x58, 0x2e, 0x72, 0xe7, 0x53, 0xce, 0x06, 0x3e, 0x65, + 0x68, 0x4a, 0xe5, 0x7c, 0x30, 0x3b, 0x28, 0xbf, 0xfb, 0x22, 0xe6, 0x92, 0x1b, 0xdb, 0x45, 0x4e, + 0xbf, 0xc4, 0x67, 0x07, 0x0f, 0x77, 0x02, 0x1e, 0x70, 0x15, 0x1f, 0x64, 0x5f, 0x79, 0xaa, 0x05, + 0x81, 0x3e, 0x64, 0x1e, 0xb9, 0x20, 0x9e, 0x3d, 0xe5, 0x78, 0x62, 0xec, 0x82, 0x7a, 0x48, 0x68, + 0x10, 0xca, 0x8e, 0xd6, 0xd5, 0x7a, 0x35, 0xa7, 0x38, 0x19, 0x0f, 0x40, 0x13, 0x09, 0x01, 0x43, + 0x94, 0x84, 0x9d, 0x5b, 0x5d, 0xad, 0xa7, 0x3b, 0x0d, 0x24, 0xc4, 0x19, 0x4a, 0x42, 0xe3, 0x11, + 0x68, 0xe5, 0x3c, 0x9f, 0x88, 0xd7, 0xa9, 0x76, 0xb5, 0x5e, 0xd3, 0x59, 0x03, 0xd6, 0x57, 0x0d, + 0x6c, 0x8d, 0x52, 0xd7, 0x41, 0xcc, 0x3b, 0xe1, 0x51, 0x44, 0xa5, 0xf1, 0x18, 0xe8, 0x89, 0x44, + 0xb1, 0x84, 0x1b, 0x44, 0x6d, 0x85, 0x9d, 0xe5, 0x6c, 0x5d, 0xa0, 0xb3, 0x34, 0x82, 0x22, 0x75, + 0x61, 0x8c, 0x98, 0xa7, 0x18, 0x6b, 0x0e, 0x60, 0x69, 0x54, 0x94, 0x32, 0x4c, 0x00, 0xb0, 0x2a, + 0x17, 0x11, 0x26, 0x15, 0xab, 0xee, 0xdc, 0x40, 0x8c, 0x3d, 0xd0, 0x22, 0x82, 0xe3, 0x10, 0xb2, + 0x34, 0xea, 0xd4, 0xd4, 0xef, 0x4d, 0x05, 0xbc, 0x4b, 0x23, 0xeb, 0x73, 0x0d, 0x34, 0x4f, 0x67, + 0xd4, 0x23, 0x0c, 0x13, 0xe3, 0x1c, 0xb4, 0x7c, 0x01, 0x5d, 0x89, 0xa1, 0x98, 0xa8, 0x5e, 0x74, + 0xfb, 0xc5, 0xd5, 0xf5, 0xfe, 0x71, 0x40, 0x65, 0x98, 0xba, 0x7d, 0xcc, 0xa3, 0x41, 0x31, 0xce, + 0x29, 0x72, 0x93, 0xa7, 0x94, 0xaf, 0x8e, 0x03, 0x39, 0x17, 0x24, 0xe9, 0xdb, 0xc3, 0xd1, 0xd1, + 0xf1, 0xb3, 0x51, 0xea, 0xbe, 0x21, 0x73, 0xa7, 0xe1, 0x0b, 0x5b, 0xe2, 0xd1, 0x24, 0x13, 0xe9, + 0x66, 0x03, 0x5d, 0x89, 0xcc, 0x15, 0xb4, 0x15, 0x56, 0x88, 0xfc, 0x00, 0x9a, 0xa5, 0x40, 0x25, + 0xc0, 0x7e, 0x79, 0x75, 0xbd, 0xff, 0xfc, 0x4f, 0x79, 0xc7, 0x38, 0x64, 0x3c, 0x8e, 0x8b, 0x81, + 0x38, 0x0d, 0x51, 0x4c, 0xe6, 0x09, 0x30, 0x30, 0x62, 0x9c, 0x51, 0x8c, 0xa6, 0xb0, 0xbc, 0xb3, + 0x9a, 0x9a, 0xd0, 0xdd, 0x32, 0xf2, 0xaa, 0xb8, 0x3c, 0x0b, 0x6c, 0xf9, 0x3c, 0x9e, 0xac, 0x13, + 0x6f, 0xab, 0xc4, 0x76, 0x06, 0xae, 0x72, 0x04, 0xd8, 0x5d, 0x57, 0x5c, 0x59, 0x0a, 0x26, 0x34, + 0xe8, 0xd4, 0xff, 0xba, 0xed, 0xd3, 0xf7, 0xe7, 0xe3, 0x31, 0x0d, 0x9c, 0x9d, 0xb2, 0xf2, 0xeb, + 0xa2, 0xf0, 0x98, 0x06, 0x86, 0x0f, 0xee, 0xa9, 0xae, 0x36, 0xc8, 0x1a, 0xff, 0x4c, 0x76, 0x27, + 0x2b, 0x7a, 0x83, 0xc7, 0xfa, 0xae, 0x81, 0xbd, 0xd5, 0x79, 0x14, 0xf3, 0xcc, 0x12, 0xf1, 0x98, + 0x06, 0x8c, 0xb2, 0x60, 0xc8, 0x7c, 0xfe, 0xff, 0xbc, 0xb1, 0xb1, 0x00, 0x99, 0x37, 0xaa, 0x9b, + 0x0b, 0x70, 0x08, 0xee, 0x47, 0x34, 0x49, 0x88, 0x07, 0x95, 0x63, 0x12, 0x88, 0x79, 0xca, 0x24, + 0x89, 0x95, 0x51, 0xaa, 0xce, 0x76, 0x1e, 0x54, 0x2b, 0x9b, 0x9c, 0xe4, 0x21, 0xfb, 0xed, 0xb7, + 0x85, 0xa9, 0x5d, 0x2e, 0x4c, 0xed, 0xe7, 0xc2, 0xd4, 0xbe, 0x2c, 0xcd, 0xca, 0xe5, 0xd2, 0xac, + 0xfc, 0x58, 0x9a, 0x95, 0x8f, 0x87, 0xbf, 0xef, 0xf7, 0x62, 0xfd, 0x9e, 0xa8, 0xd6, 0xdd, 0xba, + 0x7a, 0x1f, 0x8e, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xa1, 0x77, 0x79, 0x70, 0x04, 0x00, + 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { @@ -406,6 +417,11 @@ func (m *PubRandCommit) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.EpochNum != 0 { + i = encodeVarintFinality(dAtA, i, uint64(m.EpochNum)) + i-- + dAtA[i] = 0x20 + } if len(m.Commitment) > 0 { i -= len(m.Commitment) copy(dAtA[i:], m.Commitment) @@ -607,6 +623,9 @@ func (m *PubRandCommit) Size() (n int) { if l > 0 { n += 1 + l + sovFinality(uint64(l)) } + if m.EpochNum != 0 { + n += 1 + sovFinality(uint64(m.EpochNum)) + } return n } @@ -895,6 +914,25 @@ func (m *PubRandCommit) Unmarshal(dAtA []byte) error { m.Commitment = []byte{} } iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNum", wireType) + } + m.EpochNum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNum |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipFinality(dAtA[iNdEx:]) diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index c546a68d..5af122cd 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -10,6 +10,7 @@ import ( types "github.com/babylonlabs-io/babylon/types" types0 "github.com/babylonlabs-io/babylon/x/btcstaking/types" + types1 "github.com/babylonlabs-io/babylon/x/epoching/types" gomock "github.com/golang/mock/gomock" ) @@ -66,20 +67,6 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetFinalityProvider(ctx, fpBTCPK int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalityProvider", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetFinalityProvider), ctx, fpBTCPK) } -// GetLastFinalizedEpoch mocks base method. -func (m *MockBTCStakingKeeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLastFinalizedEpoch", ctx) - ret0, _ := ret[0].(uint64) - return ret0 -} - -// GetLastFinalizedEpoch indicates an expected call of GetLastFinalizedEpoch. -func (mr *MockBTCStakingKeeperMockRecorder) GetLastFinalizedEpoch(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastFinalizedEpoch", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetLastFinalizedEpoch), ctx) -} - // GetParams mocks base method. func (m *MockBTCStakingKeeper) GetParams(ctx context.Context) types0.Params { m.ctrl.T.Helper() @@ -191,6 +178,57 @@ func (mr *MockBTCStakingKeeperMockRecorder) SlashFinalityProvider(ctx, fpBTCPK i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashFinalityProvider", reflect.TypeOf((*MockBTCStakingKeeper)(nil).SlashFinalityProvider), ctx, fpBTCPK) } +// MockCheckpointingKeeper is a mock of CheckpointingKeeper interface. +type MockCheckpointingKeeper struct { + ctrl *gomock.Controller + recorder *MockCheckpointingKeeperMockRecorder +} + +// MockCheckpointingKeeperMockRecorder is the mock recorder for MockCheckpointingKeeper. +type MockCheckpointingKeeperMockRecorder struct { + mock *MockCheckpointingKeeper +} + +// NewMockCheckpointingKeeper creates a new mock instance. +func NewMockCheckpointingKeeper(ctrl *gomock.Controller) *MockCheckpointingKeeper { + mock := &MockCheckpointingKeeper{ctrl: ctrl} + mock.recorder = &MockCheckpointingKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCheckpointingKeeper) EXPECT() *MockCheckpointingKeeperMockRecorder { + return m.recorder +} + +// GetEpoch mocks base method. +func (m *MockCheckpointingKeeper) GetEpoch(ctx context.Context) *types1.Epoch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpoch", ctx) + ret0, _ := ret[0].(*types1.Epoch) + return ret0 +} + +// GetEpoch indicates an expected call of GetEpoch. +func (mr *MockCheckpointingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpoch", reflect.TypeOf((*MockCheckpointingKeeper)(nil).GetEpoch), ctx) +} + +// GetLastFinalizedEpoch mocks base method. +func (m *MockCheckpointingKeeper) GetLastFinalizedEpoch(ctx context.Context) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLastFinalizedEpoch", ctx) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetLastFinalizedEpoch indicates an expected call of GetLastFinalizedEpoch. +func (mr *MockCheckpointingKeeperMockRecorder) GetLastFinalizedEpoch(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastFinalizedEpoch", reflect.TypeOf((*MockCheckpointingKeeper)(nil).GetLastFinalizedEpoch), ctx) +} + // MockIncentiveKeeper is a mock of IncentiveKeeper interface. type MockIncentiveKeeper struct { ctrl *gomock.Controller diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index a0f7f327..76aa6850 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -258,6 +258,8 @@ type PubRandCommitResponse struct { NumPubRand uint64 `protobuf:"varint,1,opt,name=num_pub_rand,json=numPubRand,proto3" json:"num_pub_rand,omitempty"` // commitment is the value of the commitment Commitment []byte `protobuf:"bytes,2,opt,name=commitment,proto3" json:"commitment,omitempty"` + // epoch_num defines the epoch number that the commit falls into + EpochNum uint64 `protobuf:"varint,3,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` } func (m *PubRandCommitResponse) Reset() { *m = PubRandCommitResponse{} } @@ -307,6 +309,13 @@ func (m *PubRandCommitResponse) GetCommitment() []byte { return nil } +func (m *PubRandCommitResponse) GetEpochNum() uint64 { + if m != nil { + return m.EpochNum + } + return 0 +} + // QueryListPubRandCommitRequest is the request type for the // Query/ListPubRandCommit RPC method. type QueryListPubRandCommitRequest struct { @@ -1147,90 +1156,91 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 1318 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdf, 0x6f, 0xdb, 0x54, - 0x14, 0xee, 0xcd, 0xd6, 0x6c, 0x3b, 0x4d, 0xb6, 0xee, 0xae, 0x1b, 0x25, 0xa3, 0x69, 0xea, 0x8d, - 0xb6, 0xb4, 0x9b, 0xbd, 0xa6, 0x65, 0x6c, 0x05, 0xb4, 0x35, 0xd0, 0xd2, 0x88, 0x2e, 0x64, 0x2e, - 0x9a, 0xb4, 0x3d, 0x60, 0xec, 0xd4, 0x49, 0xad, 0x26, 0xbe, 0x5e, 0xec, 0x44, 0x0d, 0xd3, 0x24, - 0x84, 0xc4, 0x1e, 0x10, 0x48, 0x48, 0xbc, 0xc0, 0xc3, 0x1e, 0x40, 0x88, 0x17, 0xfe, 0x0f, 0xb4, - 0xc7, 0x69, 0xf0, 0x80, 0x26, 0x51, 0xa1, 0x96, 0x3f, 0x04, 0xe5, 0xde, 0xeb, 0xc4, 0x4e, 0x9c, - 0x1f, 0x2d, 0x11, 0x6f, 0xf1, 0xf5, 0xf9, 0xf1, 0x7d, 0xdf, 0x39, 0x3e, 0xf7, 0xb4, 0x30, 0xa9, - 0xa9, 0x5a, 0xad, 0x48, 0x4c, 0x29, 0x6f, 0x98, 0x6a, 0xd1, 0x70, 0x6a, 0x52, 0x75, 0x41, 0x7a, - 0x58, 0xd1, 0xcb, 0x35, 0xd1, 0x2a, 0x13, 0x87, 0xe0, 0x73, 0xdc, 0x40, 0x74, 0x0d, 0xc4, 0xea, - 0x42, 0x6c, 0xac, 0x40, 0x0a, 0x84, 0xbe, 0x97, 0xea, 0xbf, 0x98, 0x69, 0xec, 0xb5, 0x02, 0x21, - 0x85, 0xa2, 0x2e, 0xa9, 0x96, 0x21, 0xa9, 0xa6, 0x49, 0x1c, 0xd5, 0x31, 0x88, 0x69, 0xf3, 0xb7, - 0x73, 0x39, 0x62, 0x97, 0x88, 0x2d, 0x69, 0xaa, 0xad, 0xb3, 0x0c, 0x52, 0x75, 0x41, 0xd3, 0x1d, - 0x75, 0x41, 0xb2, 0xd4, 0x82, 0x61, 0x52, 0x63, 0x6e, 0x9b, 0x08, 0x42, 0x65, 0xa9, 0x65, 0xb5, - 0xe4, 0x46, 0x13, 0x82, 0x2c, 0x1a, 0x10, 0xa9, 0x8d, 0x30, 0x06, 0xf8, 0x6e, 0x3d, 0x4f, 0x96, - 0x3a, 0xca, 0xfa, 0xc3, 0x8a, 0x6e, 0x3b, 0x42, 0x16, 0xce, 0xf9, 0x4e, 0x6d, 0x8b, 0x98, 0xb6, - 0x8e, 0x6f, 0x42, 0x98, 0x25, 0x18, 0x47, 0x09, 0x34, 0x3b, 0x92, 0xbc, 0x28, 0x06, 0x10, 0x17, - 0x99, 0x53, 0xea, 0xf8, 0xb3, 0xbd, 0xc9, 0x21, 0x99, 0x3b, 0x08, 0xdf, 0x20, 0x48, 0xd0, 0x90, - 0x1b, 0x86, 0xed, 0x64, 0x2b, 0x5a, 0xd1, 0xc8, 0xc9, 0xaa, 0xb9, 0x45, 0x4a, 0xa6, 0x6e, 0xbb, - 0x69, 0xf1, 0x14, 0x44, 0xf3, 0x96, 0xa2, 0x39, 0x39, 0xc5, 0xda, 0x51, 0xb6, 0xf5, 0x5d, 0x9a, - 0xe6, 0x94, 0x0c, 0x79, 0x2b, 0xe5, 0xe4, 0xb2, 0x3b, 0xeb, 0xfa, 0x2e, 0x5e, 0x03, 0x68, 0x2a, - 0x31, 0x1e, 0xa2, 0x30, 0xa6, 0x45, 0x26, 0x9b, 0x58, 0x97, 0x4d, 0x64, 0x85, 0xe1, 0xb2, 0x89, - 0x59, 0xb5, 0xa0, 0xf3, 0xf0, 0xb2, 0xc7, 0x53, 0x78, 0x11, 0x82, 0xa9, 0x2e, 0x78, 0x38, 0xe1, - 0x9f, 0x11, 0x44, 0xac, 0x8a, 0xa6, 0x94, 0x55, 0x73, 0x4b, 0x29, 0xa9, 0xd6, 0x38, 0x4a, 0x1c, - 0x9b, 0x1d, 0x49, 0xae, 0x05, 0xf2, 0xee, 0x19, 0x4e, 0xcc, 0x56, 0xb4, 0xfa, 0xe9, 0x1d, 0xd5, - 0x5a, 0x35, 0x9d, 0x72, 0x2d, 0xb5, 0xfc, 0x72, 0x6f, 0xf2, 0x7a, 0xc1, 0x70, 0xb6, 0x2b, 0x9a, - 0x98, 0x23, 0x25, 0x89, 0x47, 0x2d, 0xaa, 0x9a, 0x7d, 0xd5, 0x20, 0xee, 0xa3, 0xe4, 0xd4, 0x2c, - 0xdd, 0x16, 0x37, 0x73, 0xdb, 0x26, 0x29, 0x97, 0x79, 0x0c, 0x19, 0xac, 0x46, 0x30, 0xfc, 0x41, - 0x80, 0x28, 0x33, 0x3d, 0x45, 0x61, 0xa0, 0xbc, 0xaa, 0xc4, 0xde, 0x85, 0x33, 0x2d, 0x18, 0xf1, - 0x28, 0x1c, 0xdb, 0xd1, 0x6b, 0xb4, 0x12, 0xc7, 0xe5, 0xfa, 0x4f, 0x3c, 0x06, 0xc3, 0x55, 0xb5, - 0x58, 0xd1, 0x69, 0xa2, 0x88, 0xcc, 0x1e, 0x96, 0x43, 0x37, 0x90, 0x70, 0x1f, 0xce, 0x73, 0xf7, - 0xf7, 0x48, 0xa9, 0x64, 0x38, 0x0d, 0x1d, 0x13, 0x10, 0x31, 0x2b, 0x25, 0xc5, 0x95, 0x92, 0x47, - 0x03, 0xb3, 0x52, 0xe2, 0xf6, 0x38, 0x0e, 0x90, 0xa3, 0x3e, 0x25, 0xdd, 0x74, 0x78, 0x64, 0xcf, - 0x89, 0xf0, 0x15, 0x82, 0x09, 0xaf, 0xc0, 0xde, 0x24, 0xff, 0x7b, 0xf3, 0xfc, 0x11, 0x82, 0x78, - 0x27, 0x30, 0x9c, 0xf1, 0x2e, 0x9c, 0x6b, 0x34, 0x0e, 0xa3, 0xe1, 0xe9, 0x9f, 0x74, 0xcf, 0xfe, - 0x69, 0x8f, 0x28, 0xfa, 0x4e, 0xdd, 0xf2, 0xc8, 0xa3, 0x56, 0xcb, 0xf1, 0xe0, 0x9a, 0x81, 0xb4, - 0x54, 0xb3, 0x4b, 0x4b, 0xdc, 0xf6, 0xb6, 0xc4, 0x48, 0x72, 0x2e, 0x78, 0x2e, 0x04, 0xd1, 0xf2, - 0xb6, 0xcf, 0x3c, 0x9c, 0xa5, 0x1a, 0xa4, 0x8a, 0x24, 0xb7, 0xe3, 0x96, 0xf5, 0x02, 0x84, 0xb7, - 0x75, 0xa3, 0xb0, 0xed, 0xf0, 0x7c, 0xfc, 0x49, 0xb8, 0xc3, 0x07, 0x17, 0x37, 0xe6, 0xb2, 0xbf, - 0x05, 0xc3, 0x5a, 0xfd, 0x80, 0x0f, 0xa8, 0xa9, 0x40, 0x20, 0x69, 0x73, 0x4b, 0xdf, 0xd5, 0xb7, - 0x98, 0x27, 0xb3, 0x17, 0x7e, 0x44, 0x70, 0xa1, 0x51, 0x00, 0xfa, 0xa6, 0x31, 0x95, 0x6e, 0x41, - 0xd8, 0x76, 0x54, 0xa7, 0xc2, 0xa6, 0xde, 0xe9, 0xe4, 0x4c, 0xc7, 0xea, 0x19, 0x3c, 0xe8, 0x26, - 0x35, 0x97, 0xb9, 0xdb, 0xc0, 0xda, 0xee, 0x29, 0x82, 0x57, 0xda, 0x30, 0x36, 0x47, 0x33, 0x25, - 0x62, 0xf3, 0x16, 0xeb, 0x83, 0x39, 0x77, 0x18, 0x58, 0xc3, 0x08, 0x8b, 0xf0, 0x2a, 0x85, 0x77, - 0x8f, 0x38, 0xba, 0xbd, 0xe2, 0xac, 0xd3, 0x42, 0xf5, 0xaa, 0x23, 0x81, 0x58, 0x90, 0x13, 0xa7, - 0x75, 0x17, 0x4e, 0xb0, 0x2f, 0x9a, 0xf1, 0x8a, 0xa4, 0x6e, 0xbc, 0xdc, 0x9b, 0x5c, 0xea, 0x77, - 0x64, 0xa6, 0xd2, 0xd9, 0xc5, 0xa5, 0x6b, 0xd9, 0x8a, 0xf6, 0xa1, 0x5e, 0x93, 0xc3, 0x5a, 0x7d, - 0x0c, 0xd8, 0xc2, 0x4d, 0x18, 0xa3, 0x09, 0x57, 0xab, 0xc6, 0x96, 0x6e, 0xe6, 0xf4, 0xfe, 0xe7, - 0x87, 0x20, 0xc3, 0xf9, 0x16, 0xd7, 0x86, 0xfa, 0x27, 0x75, 0x7e, 0xc6, 0x3b, 0x6f, 0x22, 0x50, - 0xff, 0x86, 0x63, 0xc3, 0x5c, 0x78, 0x82, 0xb8, 0x6a, 0xf5, 0xa2, 0xba, 0xef, 0x3d, 0x37, 0x62, - 0xc4, 0x76, 0xd4, 0xb2, 0xa3, 0xf8, 0xb4, 0x1b, 0xa1, 0x67, 0x4c, 0xaa, 0x81, 0x75, 0xd7, 0x4f, - 0x88, 0x57, 0xa2, 0x05, 0x08, 0xa7, 0xf8, 0x36, 0x9c, 0x72, 0x31, 0xbb, 0x3d, 0xd6, 0x83, 0x63, - 0xd3, 0x7e, 0x70, 0x2d, 0xf6, 0x0e, 0xff, 0x02, 0x36, 0x8d, 0x82, 0x69, 0x98, 0x85, 0xb4, 0x99, - 0x27, 0x87, 0xa8, 0xdf, 0x67, 0x30, 0xde, 0xee, 0xcd, 0xf9, 0x7d, 0x02, 0x67, 0xf2, 0x96, 0x62, - 0xb3, 0x37, 0x8a, 0x61, 0xe6, 0x09, 0xaf, 0xe4, 0xb5, 0x40, 0x96, 0x6b, 0xfc, 0x77, 0xb6, 0x4c, - 0xea, 0x2c, 0xcb, 0x9e, 0x90, 0x7c, 0xf3, 0x89, 0xe6, 0x2d, 0xcf, 0xa1, 0xa0, 0xb5, 0xe7, 0x6e, - 0x54, 0xd9, 0x5f, 0x42, 0x74, 0xe4, 0x12, 0xfe, 0xe6, 0xf6, 0x92, 0x3f, 0x09, 0x67, 0xf8, 0x29, - 0x8c, 0xb6, 0x30, 0x74, 0x0b, 0x79, 0x54, 0x8a, 0xa7, 0x7d, 0x14, 0x07, 0x57, 0xe6, 0xb9, 0x5b, - 0x6c, 0xb8, 0xfb, 0xe7, 0x29, 0x3e, 0x0b, 0xd1, 0xcc, 0x47, 0x19, 0x65, 0x2d, 0x9d, 0x59, 0xd9, - 0x48, 0x3f, 0x58, 0x7d, 0x7f, 0x74, 0x08, 0x47, 0xe1, 0x54, 0xf3, 0x11, 0xe1, 0x13, 0x70, 0x6c, - 0x25, 0x73, 0x7f, 0x34, 0x94, 0xfc, 0x32, 0x0a, 0xc3, 0x54, 0x09, 0xfc, 0x39, 0x82, 0x30, 0xdb, - 0x48, 0x71, 0xe7, 0xc1, 0xed, 0x5f, 0x7f, 0x63, 0xb3, 0xbd, 0x0d, 0x19, 0x68, 0xe1, 0xd2, 0x17, - 0xbf, 0xff, 0xf3, 0x5d, 0x68, 0x02, 0x5f, 0x94, 0x3a, 0x6f, 0xe3, 0xf8, 0x2f, 0x04, 0x63, 0x41, - 0x7b, 0x21, 0x7e, 0xf3, 0xb0, 0x7b, 0x24, 0x83, 0x77, 0xfd, 0x68, 0xeb, 0xa7, 0x70, 0x8f, 0x82, - 0xcd, 0xe2, 0x8c, 0xd4, 0xed, 0x0f, 0x03, 0xc5, 0xe2, 0xf5, 0xb6, 0xa5, 0x47, 0xbe, 0x0f, 0xea, - 0xb1, 0x64, 0xd1, 0xc8, 0x74, 0xa9, 0x61, 0xa1, 0x95, 0xa2, 0x61, 0x3b, 0xf8, 0x05, 0x82, 0xb3, - 0x6d, 0x7b, 0x0b, 0x4e, 0x1e, 0x6a, 0xc9, 0x61, 0xcc, 0x16, 0x8f, 0xb0, 0x18, 0x09, 0x1f, 0x53, - 0x5a, 0x19, 0xbc, 0xf1, 0x1f, 0x68, 0xf9, 0x16, 0x35, 0x4a, 0xea, 0x09, 0x82, 0x61, 0xda, 0x7c, - 0x78, 0xba, 0x33, 0x28, 0xef, 0xa6, 0x12, 0x9b, 0xe9, 0x69, 0xc7, 0x01, 0x5f, 0xa1, 0x80, 0xa7, - 0xf1, 0xe5, 0x40, 0xc0, 0xec, 0x56, 0x96, 0x1e, 0xb1, 0x89, 0xff, 0x18, 0x7f, 0x8d, 0x00, 0x9a, - 0x17, 0x3e, 0x9e, 0xef, 0x2e, 0x91, 0x6f, 0x75, 0x89, 0x5d, 0xe9, 0xcf, 0xb8, 0xaf, 0x66, 0xe6, - 0xdb, 0xc2, 0x53, 0x04, 0x51, 0xdf, 0x5d, 0x8d, 0xc5, 0xce, 0x49, 0x82, 0x36, 0x81, 0x98, 0xd4, - 0xb7, 0x3d, 0xc7, 0x35, 0x4f, 0x71, 0xbd, 0x8e, 0x2f, 0x05, 0xe2, 0xaa, 0xd6, 0x7d, 0x9a, 0x72, - 0xfd, 0x8a, 0xe0, 0xa4, 0x7b, 0x05, 0xe1, 0x37, 0x3a, 0xa7, 0x6a, 0xb9, 0xfe, 0x63, 0x73, 0xfd, - 0x98, 0x72, 0x40, 0xeb, 0x14, 0x50, 0x0a, 0xdf, 0x3e, 0x6a, 0xc7, 0xb9, 0x37, 0x23, 0xfe, 0x1e, - 0x41, 0xd4, 0x77, 0xdf, 0x76, 0x53, 0x33, 0x68, 0x43, 0xe8, 0xa6, 0x66, 0xe0, 0x45, 0x2e, 0x4c, - 0x53, 0xf0, 0x09, 0x1c, 0x0f, 0x04, 0xdf, 0xbc, 0xb3, 0x7f, 0x41, 0x30, 0xe2, 0x99, 0xee, 0xb8, - 0x4b, 0x2f, 0xb5, 0xdf, 0xc6, 0xb1, 0xab, 0x7d, 0x5a, 0x73, 0x50, 0xcb, 0x14, 0xd4, 0x12, 0x4e, - 0x06, 0x82, 0xf2, 0xdd, 0x59, 0xad, 0x62, 0xe2, 0x1f, 0x10, 0x44, 0x7c, 0xd7, 0x50, 0x7f, 0xb9, - 0x1b, 0x0a, 0x8a, 0xfd, 0x9a, 0x73, 0xac, 0x73, 0x14, 0xeb, 0x65, 0x2c, 0xf4, 0xc6, 0x9a, 0xda, - 0x78, 0xb6, 0x1f, 0x47, 0xcf, 0xf7, 0xe3, 0xe8, 0xef, 0xfd, 0x38, 0xfa, 0xf6, 0x20, 0x3e, 0xf4, - 0xfc, 0x20, 0x3e, 0xf4, 0xe7, 0x41, 0x7c, 0xe8, 0x41, 0xb2, 0xf7, 0x12, 0xbb, 0xdb, 0x0c, 0x4c, - 0xf7, 0x59, 0x2d, 0x4c, 0xff, 0x67, 0xb3, 0xf8, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x6c, - 0x72, 0x7b, 0x91, 0x12, 0x00, 0x00, + // 1338 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdf, 0x6f, 0xd3, 0xd6, + 0x17, 0xef, 0x6d, 0x69, 0xa1, 0xa7, 0x09, 0x94, 0x4b, 0xe1, 0xdb, 0x6f, 0x18, 0x69, 0x30, 0xac, + 0xb0, 0x02, 0x36, 0x4d, 0x19, 0x03, 0xb6, 0x09, 0xc8, 0x46, 0x47, 0xb4, 0x92, 0x05, 0x33, 0x21, + 0x8d, 0x87, 0x79, 0x76, 0x7a, 0x93, 0x58, 0x8d, 0x7d, 0x4d, 0x6c, 0x47, 0xcd, 0x10, 0xd2, 0x34, + 0x69, 0x3c, 0x4c, 0x9b, 0x34, 0x69, 0x2f, 0xdb, 0x03, 0x0f, 0x9b, 0xa6, 0xbd, 0xec, 0xff, 0x98, + 0x78, 0x44, 0x6c, 0x0f, 0x13, 0xd2, 0xd0, 0x04, 0xfb, 0x43, 0xa6, 0xdc, 0x7b, 0x9d, 0xd8, 0x89, + 0xf3, 0x83, 0x2e, 0xda, 0x5b, 0x7c, 0x7c, 0x7e, 0x7c, 0xce, 0xe7, 0x1c, 0x9f, 0x73, 0x5a, 0x58, + 0x32, 0x74, 0xa3, 0x59, 0xa3, 0xb6, 0x52, 0x36, 0x6d, 0xbd, 0x66, 0x7a, 0x4d, 0xa5, 0xb1, 0xaa, + 0xdc, 0xf5, 0x49, 0xbd, 0x29, 0x3b, 0x75, 0xea, 0x51, 0x7c, 0x40, 0x28, 0xc8, 0x81, 0x82, 0xdc, + 0x58, 0x4d, 0x2d, 0x54, 0x68, 0x85, 0xb2, 0xf7, 0x4a, 0xeb, 0x17, 0x57, 0x4d, 0xbd, 0x52, 0xa1, + 0xb4, 0x52, 0x23, 0x8a, 0xee, 0x98, 0x8a, 0x6e, 0xdb, 0xd4, 0xd3, 0x3d, 0x93, 0xda, 0xae, 0x78, + 0xbb, 0x52, 0xa2, 0xae, 0x45, 0x5d, 0xc5, 0xd0, 0x5d, 0xc2, 0x23, 0x28, 0x8d, 0x55, 0x83, 0x78, + 0xfa, 0xaa, 0xe2, 0xe8, 0x15, 0xd3, 0x66, 0xca, 0x42, 0x37, 0x13, 0x87, 0xca, 0xd1, 0xeb, 0xba, + 0x15, 0x78, 0x93, 0xe2, 0x34, 0xda, 0x10, 0x99, 0x8e, 0xb4, 0x00, 0xf8, 0x66, 0x2b, 0x4e, 0x91, + 0x19, 0xaa, 0xe4, 0xae, 0x4f, 0x5c, 0x4f, 0x2a, 0xc2, 0x81, 0x88, 0xd4, 0x75, 0xa8, 0xed, 0x12, + 0x7c, 0x11, 0x66, 0x78, 0x80, 0x45, 0x94, 0x41, 0x27, 0xe7, 0xb2, 0x87, 0xe5, 0x98, 0xc4, 0x65, + 0x6e, 0x94, 0xdb, 0xf5, 0xe8, 0xd9, 0xd2, 0x84, 0x2a, 0x0c, 0xa4, 0xaf, 0x11, 0x64, 0x98, 0xcb, + 0x0d, 0xd3, 0xf5, 0x8a, 0xbe, 0x51, 0x33, 0x4b, 0xaa, 0x6e, 0x6f, 0x52, 0xcb, 0x26, 0x6e, 0x10, + 0x16, 0x1f, 0x85, 0x64, 0xd9, 0xd1, 0x0c, 0xaf, 0xa4, 0x39, 0x5b, 0x5a, 0x95, 0x6c, 0xb3, 0x30, + 0xb3, 0x2a, 0x94, 0x9d, 0x9c, 0x57, 0x2a, 0x6e, 0x5d, 0x27, 0xdb, 0x78, 0x1d, 0xa0, 0xc3, 0xc4, + 0xe2, 0x24, 0x83, 0xb1, 0x2c, 0x73, 0xda, 0xe4, 0x16, 0x6d, 0x32, 0x2f, 0x8c, 0xa0, 0x4d, 0x2e, + 0xea, 0x15, 0x22, 0xdc, 0xab, 0x21, 0x4b, 0xe9, 0xc9, 0x24, 0x1c, 0x1d, 0x80, 0x47, 0x24, 0xfc, + 0x13, 0x82, 0x84, 0xe3, 0x1b, 0x5a, 0x5d, 0xb7, 0x37, 0x35, 0x4b, 0x77, 0x16, 0x51, 0x66, 0xea, + 0xe4, 0x5c, 0x76, 0x3d, 0x36, 0xef, 0xa1, 0xee, 0xe4, 0xa2, 0x6f, 0xb4, 0xa4, 0x37, 0x74, 0xe7, + 0x9a, 0xed, 0xd5, 0x9b, 0xb9, 0x4b, 0x4f, 0x9f, 0x2d, 0x9d, 0xaf, 0x98, 0x5e, 0xd5, 0x37, 0xe4, + 0x12, 0xb5, 0x14, 0xe1, 0xb5, 0xa6, 0x1b, 0xee, 0x19, 0x93, 0x06, 0x8f, 0x8a, 0xd7, 0x74, 0x88, + 0x2b, 0xdf, 0x2a, 0x55, 0x6d, 0x5a, 0xaf, 0x0b, 0x1f, 0x2a, 0x38, 0x6d, 0x67, 0xf8, 0xbd, 0x18, + 0x52, 0x4e, 0x0c, 0x25, 0x85, 0x83, 0x0a, 0xb3, 0x92, 0x7a, 0x1b, 0xf6, 0x75, 0x61, 0xc4, 0xf3, + 0x30, 0xb5, 0x45, 0x9a, 0xac, 0x12, 0xbb, 0xd4, 0xd6, 0x4f, 0xbc, 0x00, 0xd3, 0x0d, 0xbd, 0xe6, + 0x13, 0x16, 0x28, 0xa1, 0xf2, 0x87, 0x4b, 0x93, 0x17, 0x90, 0xd4, 0x80, 0x83, 0xc2, 0xfc, 0x1d, + 0x6a, 0x59, 0xa6, 0xd7, 0xe6, 0x31, 0x03, 0x09, 0xdb, 0xb7, 0xb4, 0x80, 0x4a, 0xe1, 0x0d, 0x6c, + 0xdf, 0x12, 0xfa, 0x38, 0x0d, 0x50, 0x62, 0x36, 0x16, 0xb1, 0x3d, 0xe1, 0x39, 0x24, 0xc1, 0x87, + 0x61, 0x96, 0x38, 0xb4, 0x54, 0xd5, 0x6c, 0xdf, 0x5a, 0x9c, 0x62, 0xe6, 0x7b, 0x98, 0xa0, 0xe0, + 0x5b, 0xd2, 0x97, 0x08, 0x8e, 0x84, 0xd9, 0x0f, 0x23, 0xf8, 0xcf, 0x3b, 0xeb, 0xf7, 0x49, 0x48, + 0xf7, 0x03, 0x23, 0xe8, 0xd8, 0x86, 0x03, 0xed, 0xae, 0xe2, 0x39, 0x86, 0x9a, 0x2b, 0x3f, 0xb4, + 0xb9, 0x7a, 0x3d, 0xca, 0x11, 0x69, 0x50, 0x3b, 0x75, 0xde, 0xe9, 0x12, 0x8f, 0xaf, 0x53, 0x68, + 0x57, 0xa9, 0x07, 0xf4, 0xcb, 0x95, 0x70, 0xbf, 0xcc, 0x65, 0x57, 0xe2, 0x87, 0x46, 0x5c, 0x5a, + 0xe1, 0xde, 0x3a, 0x05, 0xfb, 0x19, 0x07, 0xb9, 0x1a, 0x2d, 0x6d, 0x05, 0x65, 0x3d, 0x04, 0x33, + 0x55, 0x62, 0x56, 0xaa, 0x9e, 0x88, 0x27, 0x9e, 0xa4, 0x1b, 0x62, 0xaa, 0x09, 0x65, 0x41, 0xfb, + 0x1b, 0x30, 0x6d, 0xb4, 0x04, 0x62, 0x7a, 0x1d, 0x8d, 0x05, 0x92, 0xb7, 0x37, 0xc9, 0x36, 0xd9, + 0xe4, 0x96, 0x5c, 0x5f, 0xfa, 0x01, 0xc1, 0xa1, 0x76, 0x01, 0xd8, 0x9b, 0xf6, 0xc8, 0xba, 0x0c, + 0x33, 0xae, 0xa7, 0x7b, 0x3e, 0x1f, 0x89, 0x7b, 0xb3, 0x27, 0xfa, 0x56, 0xcf, 0x14, 0x4e, 0x6f, + 0x31, 0x75, 0x55, 0x98, 0x8d, 0xad, 0xed, 0x1e, 0x22, 0xf8, 0x5f, 0x0f, 0xc6, 0xce, 0xdc, 0x66, + 0x89, 0xb8, 0xa2, 0xc5, 0x46, 0xc8, 0x5c, 0x18, 0x8c, 0xad, 0x61, 0xa4, 0x35, 0xf8, 0x3f, 0x83, + 0x77, 0x9b, 0x7a, 0xc4, 0xbd, 0xea, 0x5d, 0x67, 0x85, 0x1a, 0x56, 0x47, 0x0a, 0xa9, 0x38, 0x23, + 0x91, 0xd6, 0x4d, 0xd8, 0xcd, 0xbf, 0x68, 0x9e, 0x57, 0x22, 0x77, 0xe1, 0xe9, 0xb3, 0xa5, 0x73, + 0xa3, 0xce, 0xd3, 0x5c, 0xbe, 0xb8, 0x76, 0xee, 0x6c, 0xd1, 0x37, 0xde, 0x27, 0x4d, 0x75, 0xc6, + 0x68, 0x8d, 0x01, 0x57, 0xba, 0x08, 0x0b, 0x2c, 0xe0, 0xb5, 0x86, 0xb9, 0x49, 0xec, 0x12, 0x19, + 0x7d, 0x7e, 0x48, 0x2a, 0x1c, 0xec, 0x32, 0x6d, 0xb3, 0xbf, 0x87, 0x08, 0x99, 0xe8, 0xbc, 0x23, + 0xb1, 0xfc, 0xb7, 0x0d, 0xdb, 0xea, 0xd2, 0x03, 0x24, 0x58, 0x6b, 0x15, 0x35, 0x78, 0x1f, 0x5a, + 0x97, 0x09, 0xd7, 0xd3, 0xeb, 0x9e, 0x16, 0xe1, 0x6e, 0x8e, 0xc9, 0x38, 0x55, 0x63, 0xeb, 0xae, + 0x1f, 0x91, 0xa8, 0x44, 0x17, 0x10, 0x91, 0xe2, 0x9b, 0x30, 0x1b, 0x60, 0x0e, 0x7a, 0x6c, 0x48, + 0x8e, 0x1d, 0xfd, 0xf1, 0xb5, 0xd8, 0x5b, 0xe2, 0x0b, 0xb8, 0x65, 0x56, 0x6c, 0xd3, 0xae, 0xe4, + 0xed, 0x32, 0x7d, 0x89, 0xfa, 0x7d, 0x0a, 0x8b, 0xbd, 0xd6, 0x22, 0xbf, 0x8f, 0x61, 0x5f, 0xd9, + 0xd1, 0x5c, 0xfe, 0x46, 0x33, 0xed, 0x32, 0x15, 0x95, 0x3c, 0x1b, 0x9b, 0xe5, 0xba, 0xf8, 0x5d, + 0xac, 0xd3, 0x56, 0x96, 0xf5, 0x90, 0x4b, 0x71, 0x16, 0x25, 0xcb, 0x4e, 0x48, 0x28, 0x19, 0xbd, + 0xb1, 0xdb, 0x55, 0x8e, 0x96, 0x10, 0xed, 0xb8, 0x84, 0xbf, 0x06, 0xbd, 0x14, 0x0d, 0x22, 0x32, + 0xfc, 0x04, 0xe6, 0xbb, 0x32, 0x0c, 0x0a, 0xb9, 0xd3, 0x14, 0xf7, 0x46, 0x52, 0x1c, 0x5f, 0x99, + 0x57, 0x2e, 0xf3, 0xe1, 0x1e, 0x9d, 0xa7, 0x78, 0x3f, 0x24, 0x0b, 0x1f, 0x14, 0xb4, 0xf5, 0x7c, + 0xe1, 0xea, 0x46, 0xfe, 0xce, 0xb5, 0x77, 0xe7, 0x27, 0x70, 0x12, 0x66, 0x3b, 0x8f, 0x08, 0xef, + 0x86, 0xa9, 0xab, 0x85, 0x8f, 0xe6, 0x27, 0xb3, 0x5f, 0x24, 0x61, 0x9a, 0x31, 0x81, 0x3f, 0x43, + 0x30, 0xc3, 0xcf, 0x55, 0xdc, 0x7f, 0x70, 0x47, 0x6f, 0xe3, 0xd4, 0xc9, 0xe1, 0x8a, 0x1c, 0xb4, + 0x74, 0xec, 0xf3, 0xdf, 0xfe, 0xfe, 0x76, 0xf2, 0x08, 0x3e, 0xac, 0xf4, 0x3f, 0xd5, 0xf1, 0x9f, + 0x08, 0x16, 0xe2, 0x8e, 0x46, 0xfc, 0xfa, 0xcb, 0x1e, 0x99, 0x1c, 0xde, 0xf9, 0x9d, 0xdd, 0xa6, + 0xd2, 0x6d, 0x06, 0xb6, 0x88, 0x0b, 0xca, 0xa0, 0xbf, 0x1a, 0x34, 0x47, 0xd4, 0xdb, 0x55, 0xee, + 0x45, 0x3e, 0xa8, 0xfb, 0x8a, 0xc3, 0x3c, 0xb3, 0xa3, 0x86, 0xbb, 0xd6, 0x6a, 0xa6, 0xeb, 0xe1, + 0x27, 0x08, 0xf6, 0xf7, 0xdc, 0x2d, 0x38, 0xfb, 0x52, 0x47, 0x0e, 0xcf, 0x6c, 0x6d, 0x07, 0x87, + 0x91, 0xf4, 0x21, 0x4b, 0xab, 0x80, 0x37, 0xfe, 0x45, 0x5a, 0x91, 0x43, 0x8d, 0x25, 0xf5, 0x00, + 0xc1, 0x34, 0x6b, 0x3e, 0xbc, 0xdc, 0x1f, 0x54, 0xf8, 0x52, 0x49, 0x9d, 0x18, 0xaa, 0x27, 0x00, + 0x9f, 0x66, 0x80, 0x97, 0xf1, 0xf1, 0x58, 0xc0, 0x7c, 0x2b, 0x2b, 0xf7, 0xf8, 0xc4, 0xbf, 0x8f, + 0xbf, 0x42, 0x00, 0x9d, 0x85, 0x8f, 0x4f, 0x0d, 0xa6, 0x28, 0x72, 0xba, 0xa4, 0x4e, 0x8f, 0xa6, + 0x3c, 0x52, 0x33, 0x8b, 0x6b, 0xe1, 0x21, 0x82, 0x64, 0x64, 0x57, 0x63, 0xb9, 0x7f, 0x90, 0xb8, + 0x4b, 0x20, 0xa5, 0x8c, 0xac, 0x2f, 0x70, 0x9d, 0x62, 0xb8, 0x5e, 0xc5, 0xc7, 0x62, 0x71, 0x35, + 0x5a, 0x36, 0x1d, 0xba, 0x7e, 0x41, 0xb0, 0x27, 0x58, 0x41, 0xf8, 0xb5, 0xfe, 0xa1, 0xba, 0xd6, + 0x7f, 0x6a, 0x65, 0x14, 0x55, 0x01, 0xe8, 0x3a, 0x03, 0x94, 0xc3, 0x57, 0x76, 0xda, 0x71, 0xc1, + 0x66, 0xc4, 0xdf, 0x21, 0x48, 0x46, 0xf6, 0xed, 0x20, 0x36, 0xe3, 0x2e, 0x84, 0x41, 0x6c, 0xc6, + 0x2e, 0x72, 0x69, 0x99, 0x81, 0xcf, 0xe0, 0x74, 0x2c, 0xf8, 0xce, 0xce, 0xfe, 0x19, 0xc1, 0x5c, + 0x68, 0xba, 0xe3, 0x01, 0xbd, 0xd4, 0xbb, 0x8d, 0x53, 0x67, 0x46, 0xd4, 0x16, 0xa0, 0x2e, 0x31, + 0x50, 0xe7, 0x70, 0x36, 0x16, 0x54, 0x64, 0x67, 0x75, 0x93, 0x89, 0xbf, 0x47, 0x90, 0x88, 0xac, + 0xa1, 0xd1, 0x62, 0xb7, 0x19, 0x94, 0x47, 0x55, 0x17, 0x58, 0x57, 0x18, 0xd6, 0xe3, 0x58, 0x1a, + 0x8e, 0x35, 0xb7, 0xf1, 0xe8, 0x79, 0x1a, 0x3d, 0x7e, 0x9e, 0x46, 0x7f, 0x3d, 0x4f, 0xa3, 0x6f, + 0x5e, 0xa4, 0x27, 0x1e, 0xbf, 0x48, 0x4f, 0xfc, 0xf1, 0x22, 0x3d, 0x71, 0x27, 0x3b, 0xfc, 0x88, + 0xdd, 0xee, 0x38, 0x66, 0xf7, 0xac, 0x31, 0xc3, 0xfe, 0xa1, 0xb3, 0xf6, 0x4f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xb3, 0x40, 0x11, 0x10, 0xae, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1842,6 +1852,11 @@ func (m *PubRandCommitResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.EpochNum != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.EpochNum)) + i-- + dAtA[i] = 0x18 + } if len(m.Commitment) > 0 { i -= len(m.Commitment) copy(dAtA[i:], m.Commitment) @@ -2563,6 +2578,9 @@ func (m *PubRandCommitResponse) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } + if m.EpochNum != 0 { + n += 1 + sovQuery(uint64(m.EpochNum)) + } return n } @@ -3354,6 +3372,25 @@ func (m *PubRandCommitResponse) Unmarshal(dAtA []byte) error { m.Commitment = []byte{} } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNum", wireType) + } + m.EpochNum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNum |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:])