Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adr-032): Add ResumeFinalityProposal and handler #242

Merged
merged 13 commits into from
Nov 20, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ nil params response

### Improvements

* [#242](https://github.com/babylonlabs-io/babylon/pull/242) Add
ResumeFinalityProposal and handler
* [#258](https://github.com/babylonlabs-io/babylon/pull/258) fix go releaser
and trigger by github action
* [#252](https://github.com/babylonlabs-io/babylon/pull/252) Fix
Expand Down
10 changes: 5 additions & 5 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import (
"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/babylonlabs-io/babylon/x/mint"
minttypes "github.com/babylonlabs-io/babylon/x/mint/types"
abci "github.com/cometbft/cometbft/abci/types"
cmtos "github.com/cometbft/cometbft/libs/os"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
Expand Down Expand Up @@ -91,13 +89,15 @@ import (
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
"github.com/spf13/cast"

"github.com/babylonlabs-io/babylon/app/ante"
"github.com/babylonlabs-io/babylon/app/upgrades"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/mint"
minttypes "github.com/babylonlabs-io/babylon/x/mint/types"

"github.com/babylonlabs-io/babylon/app/ante"
appkeepers "github.com/babylonlabs-io/babylon/app/keepers"
appparams "github.com/babylonlabs-io/babylon/app/params"
"github.com/babylonlabs-io/babylon/app/upgrades"
"github.com/babylonlabs-io/babylon/client/docs"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/btccheckpoint"
btccheckpointtypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
"github.com/babylonlabs-io/babylon/x/btclightclient"
Expand Down
21 changes: 21 additions & 0 deletions proto/babylon/finality/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ service Msg {
// UnjailFinalityProvider defines a method for unjailing a jailed
// finality provider, thus it can receive voting power
rpc UnjailFinalityProvider(MsgUnjailFinalityProvider) returns (MsgUnjailFinalityProviderResponse);
// ResumeFinalityProposal handles the proposal of resuming finality.
rpc ResumeFinalityProposal(MsgResumeFinalityProposal) returns (MsgResumeFinalityProposalResponse);
}

// MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS
Expand Down Expand Up @@ -103,3 +105,22 @@ message MsgUnjailFinalityProvider {

// MsgUnjailFinalityProviderResponse defines the Msg/UnjailFinalityProvider response type
message MsgUnjailFinalityProviderResponse {}

// MsgResumeFinalityProposal is a governance proposal to resume finality from halting
message MsgResumeFinalityProposal {
option (cosmos.msg.v1.signer) = "authority";

// authority is the address of the governance account.
// just FYI: cosmos.AddressString marks that this field should use type alias
// for AddressString instead of string, but the functionality is not yet implemented
// in cosmos-proto
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// fp_pks_hex is a list of finality provider public keys to jail
// the public key follows encoding in BIP-340 spec
repeated string fp_pks_hex = 2;
// halting_height is the height where the finality halting begins
uint32 halting_height = 3;
}

// MsgResumeFinalityProposalResponse is the response to the MsgResumeFinalityProposal message.
message MsgResumeFinalityProposalResponse {}
2 changes: 1 addition & 1 deletion test/e2e/upgrades/v1.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
}
}
3 changes: 2 additions & 1 deletion x/btcstaking/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"fmt"
"math"

sdk "github.com/cosmos/cosmos-sdk/types"

bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// InitGenesis initializes the module's state from a provided genesis state.
Expand Down
3 changes: 2 additions & 1 deletion x/btcstaking/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ type (
AllowedStakingTxHashesKeySet collections.KeySet[[]byte]

btcNet *chaincfg.Params
// the address capable of executing a MsgUpdateParams message. Typically, this
// the address capable of executing a MsgUpdateParams or
// MsgResumeFinalityProposal message. Typically, this
// should be the x/gov module account.
authority string
}
Expand Down
23 changes: 23 additions & 0 deletions x/finality/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,29 @@ message MsgUpdateParams {
}
```

### MsgResumeFinalityProposal

The `MsgResumeFinalityProposal` message is used for resuming finality in case
of finality halting. It can only be executed via a governance proposal.

```protobuf
// MsgResumeFinalityProposal is a governance proposal to resume finality from halting
message MsgResumeFinalityProposal {
option (cosmos.msg.v1.signer) = "authority";

// authority is the address of the governance account.
// just FYI: cosmos.AddressString marks that this field should use type alias
// for AddressString instead of string, but the functionality is not yet implemented
// in cosmos-proto
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// fp_pks_hex is a list of finality provider public keys to jail
// the public key follows encoding in BIP-340 spec
repeated string fp_pks_hex = 2;
// halting_height is the height where the finality halting begins
uint32 halting_height = 3;
}
```

## BeginBlocker

Upon `EndBlocker`, the Finality module of each Babylon node will [execute the
Expand Down
91 changes: 91 additions & 0 deletions x/finality/keeper/gov.go
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
}
110 changes: 110 additions & 0 deletions x/finality/keeper/gov_test.go
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)
}
}
14 changes: 14 additions & 0 deletions x/finality/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara
return &types.MsgUpdateParamsResponse{}, nil
}

// ResumeFinalityProposal handles the proposal for resuming finality from halting
func (ms msgServer) ResumeFinalityProposal(goCtx context.Context, req *types.MsgResumeFinalityProposal) (*types.MsgResumeFinalityProposalResponse, error) {
if ms.authority != req.Authority {
return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority)
}

ctx := sdk.UnwrapSDKContext(goCtx)
if err := ms.HandleResumeFinalityProposal(ctx, req.FpPksHex, req.HaltingHeight); err != nil {
return nil, govtypes.ErrInvalidProposalMsg.Wrapf("failed to handle resume finality proposal: %v", err)
}

return &types.MsgResumeFinalityProposalResponse{}, nil
}

// AddFinalitySig adds a new vote to a given block
func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinalitySig) (*types.MsgAddFinalitySigResponse, error) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricsKeyAddFinalitySig)
Expand Down
1 change: 1 addition & 0 deletions x/finality/keeper/tallying.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func tally(fpSet map[string]uint64, voterBTCPKs map[string]struct{}) bool {
votedPower += power
}
}

return votedPower*3 > totalPower*2
}

Expand Down
2 changes: 2 additions & 0 deletions x/finality/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgCommitPubRandList{}, "finality/MsgCommitPubRandList", nil)
cdc.RegisterConcrete(&MsgAddFinalitySig{}, "finality/MsgAddFinalitySig", nil)
cdc.RegisterConcrete(&MsgUpdateParams{}, "finality/MsgUpdateParams", nil)
cdc.RegisterConcrete(&MsgResumeFinalityProposal{}, "finality/MsgResumeFinalityProposal", nil)
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand All @@ -20,6 +21,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
&MsgCommitPubRandList{},
&MsgAddFinalitySig{},
&MsgUpdateParams{},
&MsgResumeFinalityProposal{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand Down
8 changes: 5 additions & 3 deletions x/finality/types/msg.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package types

import (
fmt "fmt"
"fmt"

"github.com/babylonlabs-io/babylon/crypto/eots"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/cometbft/cometbft/crypto/merkle"
"github.com/cometbft/cometbft/crypto/tmhash"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/babylonlabs-io/babylon/crypto/eots"
bbn "github.com/babylonlabs-io/babylon/types"
)

// ensure that these message types implement the sdk.Msg interface
var (
_ sdk.Msg = &MsgResumeFinalityProposal{}
_ sdk.Msg = &MsgUpdateParams{}
_ sdk.Msg = &MsgAddFinalitySig{}
_ sdk.Msg = &MsgCommitPubRandList{}
Expand Down
Loading