Skip to content

Commit

Permalink
feat: Consumer FP cascaded slashing (babylonlabs-io#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
gusin13 authored Sep 25, 2024
1 parent a6f728b commit a98269d
Show file tree
Hide file tree
Showing 14 changed files with 895 additions and 193 deletions.
22 changes: 17 additions & 5 deletions proto/babylon/btcstaking/v1/events.proto
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
syntax = "proto3";
package babylon.btcstaking.v1;

import "gogoproto/gogo.proto";
import "babylon/btcstaking/v1/btcstaking.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/babylonlabs-io/babylon/x/btcstaking/types";

// EventNewFinalityProvider is the event emitted when a finality provider is created
message EventNewFinalityProvider { FinalityProvider fp = 1; }
message EventNewFinalityProvider {
FinalityProvider fp = 1;
}

// EventBTCDelegationStateUpdate is the event emitted when a BTC delegation's state is
// updated. There are the following possible state transitions:
Expand Down Expand Up @@ -37,19 +39,27 @@ message EventPowerDistUpdate {
// is slashed
// TODO: unify with existing slashing events
message EventSlashedFinalityProvider {
bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ];
bytes pk = 1 [(gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey"];
}

// EventSlashedBTCDelegation is emitted for each BTC delegation that restakes to a slashed consumer finality provider.
// It indicates that the voting power of affected Babylon finality providers will be discounted for this delegation.
message EventSlashedBTCDelegation {
// staking_tx_hash is the hash of the staking tx.
// It uniquely identifies a BTC delegation
string staking_tx_hash = 1;
}

// EventJailedFinalityProvider defines an event that a finality provider
// is jailed after being detected sluggish
message EventJailedFinalityProvider {
bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ];
bytes pk = 1 [(gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey"];
}

// EventUnjailedFinalityProvider defines an event that a jailed finality provider
// is unjailed after the jailing period is passed
message EventUnjailedFinalityProvider {
bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ];
bytes pk = 1 [(gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey"];
}

// ev is the event that affects voting power distribution
Expand All @@ -62,5 +72,7 @@ message EventPowerDistUpdate {
EventUnjailedFinalityProvider unjailed_fp = 3;
// btc_del_state_update means a BTC delegation's state is updated
EventBTCDelegationStateUpdate btc_del_state_update = 4;
// slashed_btc_delegation represents the affected BTC delegation when a consumer FP is slashed
EventSlashedBTCDelegation slashed_btc_delegation = 5;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ func (bc *BabylonController) QueryFinalityProviderSlashed(fpPk *btcec.PublicKey)
return slashed, nil
}

func (bc *BabylonController) QueryFinalityProvider(fpBtcPkHex string) (*btcstakingtypes.QueryFinalityProviderResponse, error) {
res, err := bc.bbnClient.QueryClient.FinalityProvider(fpBtcPkHex)
if err != nil {
return nil, fmt.Errorf("failed to query the finality provider %s: %v", fpBtcPkHex, err)
}

return res, nil
}

func (bc *BabylonController) QueryNodeStatus() (*coretypes.ResultStatus, error) {
return bc.bbnClient.QueryClient.GetStatus()
}
Expand Down
175 changes: 156 additions & 19 deletions test/e2e/bcd_consumer_integration/clientcontroller/cosmwasm/cosmwasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import (
"context"
"encoding/json"
"fmt"
"math/rand"
"sort"
"strings"

sdkErr "cosmossdk.io/errors"
wasmdparams "github.com/CosmWasm/wasmd/app/params"
wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/babylonlabs-io/babylon/crypto/eots"
cwconfig "github.com/babylonlabs-io/babylon/test/e2e/bcd_consumer_integration/clientcontroller/config"
"github.com/babylonlabs-io/babylon/test/e2e/bcd_consumer_integration/clientcontroller/types"
"github.com/babylonlabs-io/babylon/testutil/datagen"
bbntypes "github.com/babylonlabs-io/babylon/types"
cwcclient "github.com/babylonlabs-io/cosmwasm-client/client"
"github.com/btcsuite/btcd/btcec/v2"
Expand Down Expand Up @@ -80,7 +83,7 @@ func (wc *CosmwasmConsumerController) reliablySendMsgs(msgs []sdk.Msg, expectedE
)
}

// CommitPubRandList commits a list of Schnorr public randomness via a MsgCommitPubRand to Babylon
// CommitPubRandList commits a list of Schnorr public randomness to contract deployed on Consumer Chain
// it returns tx hash and error
func (wc *CosmwasmConsumerController) CommitPubRandList(
fpPk *btcec.PublicKey,
Expand Down Expand Up @@ -116,28 +119,90 @@ func (wc *CosmwasmConsumerController) CommitPubRandList(
return &types.TxResponse{TxHash: res.TxHash}, nil
}

// SubmitFinalitySig submits the finality signature via a MsgAddVote to Babylon
func (wc *CosmwasmConsumerController) SubmitFinalitySig(
fpPk *btcec.PublicKey,
block *types.BlockInfo,
pubRand *btcec.FieldVal,
proof []byte, // TODO: have a type for proof
sig *btcec.ModNScalar,
fpSK *btcec.PrivateKey,
fpBtcPk *btcec.PublicKey,
privateRand *eots.PrivateRand,
pubRand *bbntypes.SchnorrPubRand,
proof *cmtcrypto.Proof,
heightToVote int64,
) (*types.TxResponse, error) {
cmtProof := cmtcrypto.Proof{}
if err := cmtProof.Unmarshal(proof); err != nil {
block, err := wc.GetCometBlock(heightToVote)
if err != nil {
return nil, err
}

msgToSign := append(sdk.Uint64ToBigEndian(uint64(heightToVote)), block.Block.AppHash...)
sig, err := eots.Sign(fpSK, privateRand, msgToSign)
if err != nil {
return nil, err
}
eotsSig := bbntypes.NewSchnorrEOTSSigFromModNScalar(sig)

submitFinalitySig := &SubmitFinalitySignature{
FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpBtcPk).MarshalHex(),
Height: uint64(heightToVote),
PubRand: pubRand.MustMarshal(),
Proof: Proof{
Total: proof.Total,
Index: proof.Index,
LeafHash: proof.LeafHash,
Aunts: proof.Aunts,
},
BlockHash: block.Block.AppHash,
Signature: eotsSig.MustMarshal(),
}

msg := ExecMsg{
SubmitFinalitySignature: &SubmitFinalitySignature{
FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex(),
Height: block.Height,
PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRand).MustMarshal(),
Proof: cmtProof,
BlockHash: block.Hash,
Signature: bbntypes.NewSchnorrEOTSSigFromModNScalar(sig).MustMarshal(),
SubmitFinalitySignature: submitFinalitySig,
}

msgBytes, err := json.Marshal(msg)
if err != nil {
return nil, err
}

res, err := wc.ExecuteContract(msgBytes)
if err != nil {
return nil, err
}

return &types.TxResponse{TxHash: res.TxHash, Events: fromCosmosEventsToBytes(res.Events)}, nil
}

func (wc *CosmwasmConsumerController) SubmitInvalidFinalitySig(
r *rand.Rand,
fpSK *btcec.PrivateKey,
fpBtcPk *btcec.PublicKey,
privateRand *eots.PrivateRand,
pubRand *bbntypes.SchnorrPubRand,
proof *cmtcrypto.Proof,
heightToVote int64,
) (*types.TxResponse, error) {
invalidAppHash := datagen.GenRandomByteArray(r, 32)
invalidMsgToSign := append(sdk.Uint64ToBigEndian(uint64(heightToVote)), invalidAppHash...)
invalidSig, err := eots.Sign(fpSK, privateRand, invalidMsgToSign)
if err != nil {
return nil, err
}
invalidEotsSig := bbntypes.NewSchnorrEOTSSigFromModNScalar(invalidSig)

submitFinalitySig := &SubmitFinalitySignature{
FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpBtcPk).MarshalHex(),
Height: uint64(heightToVote),
PubRand: pubRand.MustMarshal(),
Proof: Proof{
Total: proof.Total,
Index: proof.Index,
LeafHash: proof.LeafHash,
Aunts: proof.Aunts,
},
BlockHash: invalidAppHash,
Signature: invalidEotsSig.MustMarshal(),
}

msg := ExecMsg{
SubmitFinalitySignature: submitFinalitySig,
}

msgBytes, err := json.Marshal(msg)
Expand Down Expand Up @@ -173,9 +238,14 @@ func (wc *CosmwasmConsumerController) SubmitBatchFinalitySigs(
FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex(),
Height: b.Height,
PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRandList[i]).MustMarshal(),
Proof: cmtProof,
BlockHash: b.Hash,
Signature: bbntypes.NewSchnorrEOTSSigFromModNScalar(sigs[i]).MustMarshal(),
Proof: Proof{
Total: cmtProof.Total,
Index: cmtProof.Index,
LeafHash: cmtProof.LeafHash,
Aunts: cmtProof.Aunts,
},
BlockHash: b.Hash,
Signature: bbntypes.NewSchnorrEOTSSigFromModNScalar(sigs[i]).MustMarshal(),
},
}

Expand Down Expand Up @@ -231,6 +301,41 @@ func (wc *CosmwasmConsumerController) QueryFinalityProviderHasPower(
return resp.Power > 0, nil
}

func (wc *CosmwasmConsumerController) QueryFinalityProviderInfo(
fpPk *btcec.PublicKey,
opts ...uint64,
) (*ConsumerFpInfoResponse, error) {
fpBtcPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex()

queryMsgStruct := QueryMsgFinalityProviderInfo{
FinalityProviderInfo: FinalityProviderInfo{
BtcPkHex: fpBtcPkHex,
},
}

if len(opts) > 0 {
queryMsgStruct.FinalityProviderInfo.Height = opts[0]
}

queryMsgBytes, err := json.Marshal(queryMsgStruct)
if err != nil {
return nil, fmt.Errorf("failed to marshal query message: %v", err)
}

dataFromContract, err := wc.QuerySmartContractState(wc.cfg.BtcStakingContractAddress, string(queryMsgBytes))
if err != nil {
return nil, err
}

var resp ConsumerFpInfoResponse
err = json.Unmarshal(dataFromContract.Data, &resp)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
}

return &resp, nil
}

func (wc *CosmwasmConsumerController) QueryFinalityProvidersByPower() (*ConsumerFpsByPowerResponse, error) {
queryMsgStruct := QueryMsgFinalityProvidersByPower{
FinalityProvidersByPower: struct{}{},
Expand Down Expand Up @@ -424,6 +529,32 @@ func (wc *CosmwasmConsumerController) QueryFinalityProviders() (*ConsumerFpsResp
return &resp, nil
}

func (wc *CosmwasmConsumerController) QueryFinalityProvider(btcPkHex string) (*SingleConsumerFpResponse, error) {
queryMsgStruct := QueryMsgFinalityProvider{
FinalityProvider: FinalityProviderQuery{
BtcPkHex: btcPkHex,
},
}

queryMsgBytes, err := json.Marshal(queryMsgStruct)
if err != nil {
return nil, fmt.Errorf("failed to marshal query message: %v", err)
}

dataFromContract, err := wc.QuerySmartContractState(wc.cfg.BtcStakingContractAddress, string(queryMsgBytes))
if err != nil {
return nil, err
}

var resp SingleConsumerFpResponse
err = json.Unmarshal(dataFromContract.Data, &resp)
if err != nil {
return nil, err
}

return &resp, nil
}

func (wc *CosmwasmConsumerController) QueryDelegations() (*ConsumerDelegationsResponse, error) {
queryMsgStruct := QueryMsgDelegations{
Delegations: struct{}{},
Expand Down Expand Up @@ -613,6 +744,12 @@ func (wc *CosmwasmConsumerController) GetCometNodeStatus() (*coretypes.ResultSta
return wc.cwClient.GetStatus()
}

// GetCometBlock gets the tendermint block at a given height
// NOTE: this function is only meant to be used in tests.
func (wc *CosmwasmConsumerController) GetCometBlock(height int64) (*coretypes.ResultBlock, error) {
return wc.cwClient.GetBlock(height)
}

// QueryIndexedBlock queries the indexed block at a given height
// NOTE: this function is only meant to be used in tests.
func (wc *CosmwasmConsumerController) QueryIndexedBlock(height uint64) (*IndexedBlock, error) {
Expand Down
39 changes: 26 additions & 13 deletions test/e2e/bcd_consumer_integration/clientcontroller/cosmwasm/msg.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package cosmwasm

import cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"

type ConsumerFpsResponse struct {
Fps []SingleConsumerFpResponse `json:"fps"`
}
Expand All @@ -12,10 +10,10 @@ type ConsumerFpsResponse struct {
// https://github.com/babylonchain/babylon-contract/blob/v0.5.3/contracts/btc-staking/src/msg.rs
// https://github.com/babylonchain/babylon-contract/blob/v0.5.3/contracts/btc-staking/schema/btc-staking.json
type SingleConsumerFpResponse struct {
BtcPkHex string `json:"btc_pk_hex"`
SlashedBabylonHeight uint64 `json:"slashed_babylon_height"`
SlashedBtcHeight uint64 `json:"slashed_btc_height"`
ConsumerId string `json:"consumer_id"`
BtcPkHex string `json:"btc_pk_hex"`
SlashedHeight uint64 `json:"slashed_height"`
SlashedBtcHeight uint64 `json:"slashed_btc_height"`
ConsumerId string `json:"consumer_id"`
}

type ConsumerDelegationsResponse struct {
Expand Down Expand Up @@ -142,13 +140,20 @@ type CommitPublicRandomness struct {
Signature []byte `json:"signature"`
}

type Proof struct {
Total int64 `json:"total"`
Index int64 `json:"index"`
LeafHash []byte `json:"leaf_hash"`
Aunts [][]byte `json:"aunts"`
}

type SubmitFinalitySignature struct {
FpPubkeyHex string `json:"fp_pubkey_hex"`
Height uint64 `json:"height"`
PubRand []byte `json:"pub_rand"`
Proof cmtcrypto.Proof `json:"proof"` // nested struct
BlockHash []byte `json:"block_hash"`
Signature []byte `json:"signature"`
FpPubkeyHex string `json:"fp_pubkey_hex"`
Height uint64 `json:"height"`
PubRand []byte `json:"pub_rand"`
Proof Proof `json:"proof"` // nested struct
BlockHash []byte `json:"block_hash"`
Signature []byte `json:"signature"`
}

type ExecMsg struct {
Expand All @@ -159,7 +164,7 @@ type ExecMsg struct {

type FinalityProviderInfo struct {
BtcPkHex string `json:"btc_pk_hex"`
Height uint64 `json:"height"`
Height uint64 `json:"height,omitempty"`
}

type QueryMsgFinalityProviderInfo struct {
Expand Down Expand Up @@ -202,6 +207,14 @@ type QueryMsgFinalityProviders struct {
FinalityProviders struct{} `json:"finality_providers"`
}

type QueryMsgFinalityProvider struct {
FinalityProvider FinalityProviderQuery `json:"finality_provider"`
}

type FinalityProviderQuery struct {
BtcPkHex string `json:"btc_pk_hex"`
}

type QueryMsgDelegations struct {
Delegations struct{} `json:"delegations"`
}
Expand Down
Loading

0 comments on commit a98269d

Please sign in to comment.