-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adr-032): Add ResumeFinalityProposal and handler (#242)
This PR introduces the `ResumeFinalityProposal` and implements the handler, which is part of [ADR-32](babylonlabs-io/pm#95). This part is quite independent and the algorithm of choosing finality providers to jail can be implemented in the future
- Loading branch information
Showing
14 changed files
with
760 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,4 @@ | |
"title": "any title", | ||
"summary": "any summary", | ||
"expedited": false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package keeper | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
bbntypes "github.com/babylonlabs-io/babylon/types" | ||
bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" | ||
) | ||
|
||
// HandleResumeFinalityProposal handles the resume finality proposal in the following steps: | ||
// 1. check the validity of the proposal | ||
// 2. jail the finality providers from the list and adjust the voting power cache from the | ||
// halting height to the current height | ||
// 3. tally blocks to ensure finality is resumed | ||
func (k Keeper) HandleResumeFinalityProposal(ctx sdk.Context, fpPksHex []string, haltingHeight uint32) error { | ||
// a valid proposal should be | ||
// 1. the halting height along with some parameterized future heights should be indeed non-finalized | ||
// 2. all the fps from the proposal should have missed the vote for the halting height | ||
// TODO introduce a parameter to define the finality has been halting for at least some heights | ||
|
||
params := k.GetParams(ctx) | ||
currentHeight := ctx.HeaderInfo().Height | ||
currentTime := ctx.HeaderInfo().Time | ||
|
||
// jail the given finality providers | ||
fpPks := make([]*bbntypes.BIP340PubKey, 0, len(fpPksHex)) | ||
for _, fpPkHex := range fpPksHex { | ||
fpPk, err := bbntypes.NewBIP340PubKeyFromHex(fpPkHex) | ||
if err != nil { | ||
return fmt.Errorf("invalid finality provider public key %s: %w", fpPkHex, err) | ||
} | ||
fpPks = append(fpPks, fpPk) | ||
|
||
voters := k.GetVoters(ctx, uint64(haltingHeight)) | ||
_, voted := voters[fpPkHex] | ||
if voted { | ||
// all the given finality providers should not have voted for the halting height | ||
return fmt.Errorf("the finality provider %s has voted for height %d", fpPkHex, haltingHeight) | ||
} | ||
|
||
err = k.jailSluggishFinalityProvider(ctx, fpPk) | ||
if err != nil && !errors.Is(err, bstypes.ErrFpAlreadyJailed) { | ||
return fmt.Errorf("failed to jail the finality provider %s: %w", fpPkHex, err) | ||
} | ||
|
||
// update signing info | ||
signInfo, err := k.FinalityProviderSigningTracker.Get(ctx, fpPk.MustMarshal()) | ||
if err != nil { | ||
return fmt.Errorf("the signing info of finality provider %s is not created: %w", fpPkHex, err) | ||
} | ||
signInfo.JailedUntil = currentTime.Add(params.JailDuration) | ||
signInfo.MissedBlocksCounter = 0 | ||
if err := k.DeleteMissedBlockBitmap(ctx, fpPk); err != nil { | ||
return fmt.Errorf("failed to remove the missed block bit map for finality provider %s: %w", fpPkHex, err) | ||
} | ||
err = k.FinalityProviderSigningTracker.Set(ctx, fpPk.MustMarshal(), signInfo) | ||
if err != nil { | ||
return fmt.Errorf("failed to set the signing info for finality provider %s: %w", fpPkHex, err) | ||
} | ||
|
||
k.Logger(ctx).Info( | ||
"finality provider is jailed in the proposal", | ||
"height", haltingHeight, | ||
"public_key", fpPkHex, | ||
) | ||
} | ||
|
||
// set the all the given finality providers voting power to 0 | ||
for h := uint64(haltingHeight); h <= uint64(currentHeight); h++ { | ||
distCache := k.GetVotingPowerDistCache(ctx, h) | ||
activeFps := distCache.GetActiveFinalityProviderSet() | ||
for _, fpToJail := range fpPks { | ||
if fp, exists := activeFps[fpToJail.MarshalHex()]; exists { | ||
fp.IsJailed = true | ||
k.SetVotingPower(ctx, fpToJail.MustMarshal(), h, 0) | ||
} | ||
} | ||
|
||
distCache.ApplyActiveFinalityProviders(params.MaxActiveFinalityProviders) | ||
|
||
// set the voting power distribution cache of the current height | ||
k.SetVotingPowerDistCache(ctx, h, distCache) | ||
} | ||
|
||
k.TallyBlocks(ctx) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"math/rand" | ||
"testing" | ||
"time" | ||
|
||
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" | ||
bbntypes "github.com/babylonlabs-io/babylon/types" | ||
"github.com/babylonlabs-io/babylon/x/finality/keeper" | ||
"github.com/babylonlabs-io/babylon/x/finality/types" | ||
) | ||
|
||
func TestHandleResumeFinalityProposal(t *testing.T) { | ||
r := rand.New(rand.NewSource(time.Now().Unix())) | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
bsKeeper := types.NewMockBTCStakingKeeper(ctrl) | ||
iKeeper := types.NewMockIncentiveKeeper(ctrl) | ||
cKeeper := types.NewMockCheckpointingKeeper(ctrl) | ||
fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper, cKeeper) | ||
|
||
haltingHeight := uint64(100) | ||
currentHeight := uint64(110) | ||
|
||
activeFpNum := 3 | ||
activeFpPks := generateNFpPks(t, r, activeFpNum) | ||
setupActiveFps(t, activeFpPks, haltingHeight, fKeeper, ctx) | ||
// set voting power table for each height, only the first fp votes | ||
votedFpPk := activeFpPks[0] | ||
for h := haltingHeight; h <= currentHeight; h++ { | ||
fKeeper.SetBlock(ctx, &types.IndexedBlock{ | ||
Height: h, | ||
AppHash: datagen.GenRandomByteArray(r, 32), | ||
Finalized: false, | ||
}) | ||
dc := types.NewVotingPowerDistCache() | ||
for i := 0; i < activeFpNum; i++ { | ||
fKeeper.SetVotingPower(ctx, activeFpPks[i].MustMarshal(), h, 1) | ||
dc.AddFinalityProviderDistInfo(&types.FinalityProviderDistInfo{ | ||
BtcPk: &activeFpPks[i], | ||
TotalBondedSat: 1, | ||
IsTimestamped: true, | ||
}) | ||
} | ||
dc.ApplyActiveFinalityProviders(uint32(activeFpNum)) | ||
votedSig, err := bbntypes.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) | ||
require.NoError(t, err) | ||
fKeeper.SetSig(ctx, h, &votedFpPk, votedSig) | ||
fKeeper.SetVotingPowerDistCache(ctx, h, dc) | ||
} | ||
|
||
// tally blocks and none of them should be finalised | ||
iKeeper.EXPECT().RewardBTCStaking(gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes() | ||
ctx = datagen.WithCtxHeight(ctx, currentHeight) | ||
fKeeper.TallyBlocks(ctx) | ||
for i := haltingHeight; i < currentHeight; i++ { | ||
ib, err := fKeeper.GetBlock(ctx, i) | ||
require.NoError(t, err) | ||
require.False(t, ib.Finalized) | ||
} | ||
|
||
// create a resume finality proposal to jail the last fp | ||
bsKeeper.EXPECT().JailFinalityProvider(ctx, gomock.Any()).Return(nil).AnyTimes() | ||
err := fKeeper.HandleResumeFinalityProposal(ctx, publicKeysToHex(activeFpPks[1:]), uint32(haltingHeight)) | ||
require.NoError(t, err) | ||
|
||
for i := haltingHeight; i < currentHeight; i++ { | ||
ib, err := fKeeper.GetBlock(ctx, i) | ||
require.NoError(t, err) | ||
require.True(t, ib.Finalized) | ||
} | ||
} | ||
|
||
func generateNFpPks(t *testing.T, r *rand.Rand, n int) []bbntypes.BIP340PubKey { | ||
fpPks := make([]bbntypes.BIP340PubKey, 0, n) | ||
for i := 0; i < n; i++ { | ||
fpPk, err := datagen.GenRandomBIP340PubKey(r) | ||
require.NoError(t, err) | ||
fpPks = append(fpPks, *fpPk) | ||
} | ||
|
||
return fpPks | ||
} | ||
|
||
func publicKeysToHex(pks []bbntypes.BIP340PubKey) []string { | ||
hexPks := make([]string, len(pks)) | ||
for i, pk := range pks { | ||
hexPks[i] = pk.MarshalHex() | ||
} | ||
return hexPks | ||
} | ||
|
||
func setupActiveFps(t *testing.T, fpPks []bbntypes.BIP340PubKey, height uint64, fKeeper *keeper.Keeper, ctx sdk.Context) { | ||
for _, fpPk := range fpPks { | ||
signingInfo := types.NewFinalityProviderSigningInfo( | ||
&fpPk, | ||
int64(height), | ||
0, | ||
) | ||
err := fKeeper.FinalityProviderSigningTracker.Set(ctx, fpPk, signingInfo) | ||
require.NoError(t, err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.