Skip to content

Commit

Permalink
add tests and fix updating prev dist cache
Browse files Browse the repository at this point in the history
  • Loading branch information
gitferry committed Aug 27, 2024
1 parent a976054 commit 7f29395
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 54 deletions.
24 changes: 5 additions & 19 deletions test/e2e/btc_staking_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,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 @@ -271,10 +256,11 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat
s.NoError(err)

// get activated height
activatedHeight := nonValidatorNode.QueryActivatedHeight()
s.Positive(activatedHeight)
_, err = nonValidatorNode.QueryCurrentHeight()
s.NoError(err)
activatedHeight, err := nonValidatorNode.QueryActivatedHeight()
s.ErrorIs(err, bstypes.ErrBTCStakingNotActivated)
fps := nonValidatorNode.QueryFinalityProviders()
s.Len(fps, 1)
s.Zero(fps[0].VotingPower)

/*
commit a number of public randomness since activatedHeight
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 @@ -6,7 +6,7 @@
"plan": {
"name": "signet-launch",
"time": "0001-01-01T00:00:00Z",
"height": "23",
"height": "22",
"info": "Msg info",
"upgraded_client_state": null
}
Expand Down
54 changes: 27 additions & 27 deletions x/btcstaking/keeper/power_dist_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ 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)
k.recordVotingPowerAndCache(ctx, dc, dc, maxActiveFps)
}
return
}
Expand All @@ -56,31 +56,44 @@ 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)
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)
k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps)
// record metrics
k.recordMetrics(newDc)
}

func (k Keeper) recordVotingPowerAndCache(ctx context.Context, dc *types.VotingPowerDistCache) {
func (k Keeper) recordVotingPowerAndCache(ctx context.Context, prevDc, newDc *types.VotingPowerDistCache, maxActiveFps uint32) *types.VotingPowerDistCache {
babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
// label fps with whether it has timestamped pub rand
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)
}

// filter out the top N finality providers and their total voting power, and
// record them in the new cache
newDc.ApplyActiveFinalityProviders(maxActiveFps)

// set voting power table for this height
for i := uint32(0); i < dc.NumActiveFps; i++ {
fp := dc.FinalityProviders[i]
for i := uint32(0); i < newDc.NumActiveFps; i++ {
fp := newDc.FinalityProviders[i]
k.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonTipHeight, fp.TotalVotingPower)
}

// find newly bonded finality providers and execute the hooks
newBondedFinalityProviders := newDc.FindNewActiveFinalityProviders(prevDc)
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()))
}
}

// set the voting power distribution cache of the current height
k.setVotingPowerDistCache(ctx, babylonTipHeight, dc)
k.setVotingPowerDistCache(ctx, babylonTipHeight, newDc)

return newDc
}

func (k Keeper) recordMetrics(dc *types.VotingPowerDistCache) {
Expand Down Expand Up @@ -112,7 +125,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
events []*types.EventPowerDistUpdate,
maxActiveFps uint32,
) *types.VotingPowerDistCache {
height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
// a map where key is finality provider's BTC PK hex and value is a list
// of BTC delegations that newly become active under this provider
activeBTCDels := map[string][]*types.BTCDelegation{}
Expand Down Expand Up @@ -236,18 +248,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
}
}

// label fps that does not have timestamped pub rand
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, height)
}

// filter out the top N finality providers and their total voting power, and
// record them in the new cache
newDc.ApplyActiveFinalityProviders(maxActiveFps)

return newDc
}

Expand Down
14 changes: 12 additions & 2 deletions x/btcstaking/keeper/power_dist_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ func FuzzBTCDelegationEvents(f *testing.F) {
btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl)
btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl)
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
Expand Down Expand Up @@ -221,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))
Expand Down
93 changes: 93 additions & 0 deletions x/btcstaking/types/incentive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package types

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestVotingPowerDistCache(t *testing.T) {
tests := []struct {
desc string
maxActiveFPs uint32
numActiveFps uint32
totalVotingPower uint64
fps []*FinalityProviderDistInfo
}{
{
desc: "all not timestamped",
maxActiveFPs: 80,
numActiveFps: 0,
totalVotingPower: 0,
fps: []*FinalityProviderDistInfo{
{
TotalVotingPower: 1000,
IsTimestamped: false,
},
{
TotalVotingPower: 2000,
IsTimestamped: false,
},
},
},
{
desc: "all timestamped",
maxActiveFPs: 80,
numActiveFps: 2,
totalVotingPower: 3000,
fps: []*FinalityProviderDistInfo{
{
TotalVotingPower: 1000,
IsTimestamped: true,
},
{
TotalVotingPower: 2000,
IsTimestamped: true,
},
},
},
{
desc: "partly timestamped",
maxActiveFPs: 80,
numActiveFps: 1,
totalVotingPower: 1000,
fps: []*FinalityProviderDistInfo{
{
TotalVotingPower: 1000,
IsTimestamped: true,
},
{
TotalVotingPower: 2000,
IsTimestamped: false,
},
},
},
{
desc: "small max active fps",
maxActiveFPs: 1,
numActiveFps: 1,
totalVotingPower: 2000,
fps: []*FinalityProviderDistInfo{
{
TotalVotingPower: 1000,
IsTimestamped: true,
},
{
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)
})
}
}
2 changes: 1 addition & 1 deletion x/finality/keeper/public_randomness.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (k Keeper) GetTimestampedPubRandCommitForHeight(ctx context.Context, fpBtcP
// ensure the finality provider's last randomness commit is already finalised by BTC timestamping
finalizedEpoch := k.GetLastFinalizedEpoch(ctx)
if finalizedEpoch == 0 {
return nil, fmt.Errorf("no finalized epoch yet")
return nil, types.ErrPubRandCommitNotBTCTimestamped.Wrapf("no finalized epoch yet")
}
if finalizedEpoch < prCommit.EpochNum {
return nil, types.ErrPubRandCommitNotBTCTimestamped.
Expand Down

0 comments on commit 7f29395

Please sign in to comment.