Skip to content

Commit

Permalink
feat(ADR-024): Enable BTC-timestamping public randomness (#51)
Browse files Browse the repository at this point in the history
This PR collects sub-PRs to close #8 and complete the whole feature
defined in
[ADR-024](https://github.com/babylonlabs-io/pm/blob/main/adr/adr-024-timestamping-public-randomness.md)
  • Loading branch information
gitferry authored Sep 4, 2024
1 parent b1e255a commit 00330e4
Show file tree
Hide file tree
Showing 54 changed files with 1,086 additions and 488 deletions.
8 changes: 7 additions & 1 deletion app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Expand All @@ -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(
Expand Down
9 changes: 9 additions & 0 deletions proto/babylon/btcstaking/v1/incentive.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions proto/babylon/finality/v1/finality.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions proto/babylon/finality/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 51 additions & 42 deletions test/e2e/btc_staking_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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
*/
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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() {
Expand Down
31 changes: 16 additions & 15 deletions test/e2e/configurer/chain/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions test/e2e/configurer/chain/queries_btcstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/upgrades/signet-launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
"title": "any title",
"summary": "any summary",
"expedited": false
}
}
4 changes: 3 additions & 1 deletion testutil/datagen/incentive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions testutil/keeper/btcstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -44,7 +44,7 @@ func BTCStakingKeeper(
runtime.NewKVStoreService(storeKey),
btclcKeeper,
btccKeeper,
ckptKeeper,
finalityKeeper,
&chaincfg.SimNetParams,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
Expand Down
3 changes: 2 additions & 1 deletion testutil/keeper/finality.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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(),
)

Expand Down
Loading

0 comments on commit 00330e4

Please sign in to comment.