Skip to content

Commit

Permalink
fix: add btc reorg > k check in end blocker (#518)
Browse files Browse the repository at this point in the history
- Update `x/btclightclient` hook to also report from which block header
is rollingback to
`AfterBTCRollBack(ctx context.Context, headerInfo *types.BTCHeaderInfo)`
-> `AfterBTCRollBack(ctx context.Context, rollbackFrom, rollbackTo
*types.BTCHeaderInfo)`
- Implement btc light client hook in `x/btcstaking` to listen
`AfterBTCRollBack` and register the largest BTC reorg
- Add check in `x/btcstaking` `EndBlocker` that verifies if
`largest_reorg > btc_confirmation_depth`
  • Loading branch information
RafilxTenfen authored Feb 26, 2025
1 parent a58a991 commit dbcc7c4
Show file tree
Hide file tree
Showing 33 changed files with 2,827 additions and 645 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ usage in `x/btcstkconsumer` queries
check of rewards
- [#480](https://github.com/babylonlabs-io/babylon/pull/480) Improve IBC packet structure
- [#516](https://github.com/babylonlabs-io/babylon/pull/516) Add `HasGenesis` interface to `epoching` module
- [#515](https://github.com/babylonlabs-io/babylon/pull/515) Add `staker_addr` to `EventBTCDelegationCreated`
- [#518](https://github.com/babylonlabs-io/babylon/pull/518) Add check BTC reorg blocks higher than `k` deep
- [#504](https://github.com/babylonlabs-io/babylon/pull/504) Add `btc-headers` IBC packet
- [#554](https://github.com/babylonlabs-io/babylon/pull/554) Improve vote extension logs
- [#556](https://github.com/babylonlabs-io/babylon/pull/556) Panic on consensus critical errors
Expand Down
2 changes: 1 addition & 1 deletion app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ func (ak *AppKeepers) InitKeepers(
checkpointingtypes.NewMultiCheckpointingHooks(epochingKeeper.Hooks(), zcKeeper.Hooks(), monitorKeeper.Hooks()),
)
btclightclientKeeper.SetHooks(
btclightclienttypes.NewMultiBTCLightClientHooks(btcCheckpointKeeper.Hooks()),
btclightclienttypes.NewMultiBTCLightClientHooks(btcCheckpointKeeper.Hooks(), ak.BTCStakingKeeper.Hooks()),
)

// wire the keepers with hooks to the app
Expand Down
1,171 changes: 781 additions & 390 deletions client/docs/swagger-ui/swagger.yaml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion proto/babylon/btclightclient/v1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ option go_package = "github.com/babylonlabs-io/babylon/x/btclightclient/types";
// of the current mainchain to which we are rolling back to.
// In other words, there is one rollback event emitted per re-org, to the
// greatest common ancestor of the old and the new fork.
message EventBTCRollBack { BTCHeaderInfo header = 1; }
message EventBTCRollBack {
BTCHeaderInfo header = 1;
BTCHeaderInfo rollback_from = 2;
}

// EventBTCRollForward is emitted on Msg/InsertHeader
// The header included in the event is the one the main chain is extended with.
Expand Down
12 changes: 12 additions & 0 deletions proto/babylon/btcstaking/v1/btcstaking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "cosmos_proto/cosmos.proto";
import "cosmos/staking/v1beta1/staking.proto";
import "babylon/btcstaking/v1/pop.proto";
import "babylon/btccheckpoint/v1/btccheckpoint.proto";
import "babylon/btclightclient/v1/btclightclient.proto";
import "amino/amino.proto";
import "google/protobuf/timestamp.proto";

Expand Down Expand Up @@ -292,3 +293,14 @@ message InclusionProof {
// proof is the Merkle proof that this tx is included in the position in `key`
bytes proof = 2;
}

// LargestBtcReOrg stores the largest BTC reorg recorded
message LargestBtcReOrg {
// BlockDiff is the difference of the block height of the BTC header Tip - the btc height
// which it was rolled back
uint32 block_diff = 1;
// RollbackFrom is the latest BTC block header prior to rollback
babylon.btclightclient.v1.BTCHeaderInfo rollback_from = 2;
// RollbackTo is the BTC block header which we rollback to
babylon.btclightclient.v1.BTCHeaderInfo rollback_to = 3;
}
20 changes: 20 additions & 0 deletions proto/babylon/btcstaking/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "cosmos/base/query/v1beta1/pagination.proto";
import "babylon/btcstaking/v1/params.proto";
import "babylon/btcstaking/v1/btcstaking.proto";
import "babylon/btcstaking/v1/pop.proto";
import "babylon/btclightclient/v1/query.proto";

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

Expand Down Expand Up @@ -67,6 +68,11 @@ service Query {
option (google.api.http).get =
"/babylon/btcstaking/v1/btc_delegation/{staking_tx_hash_hex}";
}

// LargestBtcReOrg retrieves the largest BTC reorg
rpc LargestBtcReOrg(QueryLargestBtcReOrgRequest) returns (QueryLargestBtcReOrgResponse) {
option (google.api.http).get = "/babylon/btcstaking/v1/largest_btc_reorg";
}
}

// QueryParamsRequest is request type for the Query/Params RPC method.
Expand Down Expand Up @@ -325,3 +331,17 @@ message FinalityProviderResponse {
// operating in the Babylon chain.
string consumer_id = 12;
}

// QueryLargestBtcReOrgRequest query request of the largest BTC reorg request
message QueryLargestBtcReOrgRequest {}

// QueryLargestBtcReOrgResponse stores the largest BTC reorg recorded
message QueryLargestBtcReOrgResponse {
// BlockDiff is the difference of the block height of the BTC header Tip - the btc height
// which it was rolled back
uint32 block_diff = 1;
// RollbackFrom is the latest BTC block header prior to rollback
babylon.btclightclient.v1.BTCHeaderInfoResponse rollback_from = 2;
// RollbackTo is the BTC block header which we rollback to
babylon.btclightclient.v1.BTCHeaderInfoResponse rollback_to = 3;
}
4 changes: 2 additions & 2 deletions proto/buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ deps:
- remote: buf.build
owner: cosmos
repository: ibc
commit: 97bbf00752ab4256901a0bb6788dc4fb
digest: shake256:f86e1215538bd0019962da96c5d65e640ff0e4bb26b4a60368233f3d91b9d0e9825beac47cec406bad5b107fa97745662e76718bde19a0c424a2a6ed71700647
commit: 3a3bfd48512842f781ded81ca6fbe72e
digest: shake256:762d7891bd40c864a0a4159439e54f7e33d41cb50090c4ab6d077c1d548ed7f89ef8802eee546ad4534ba18b6f0687fb6b8a06945c509bffc686ee60d1016ada
- remote: buf.build
owner: cosmos
repository: ics23
Expand Down
3 changes: 2 additions & 1 deletion x/btccheckpoint/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"

ltypes "github.com/babylonlabs-io/babylon/x/btclightclient/types"
etypes "github.com/babylonlabs-io/babylon/x/epoching/types"
)
Expand All @@ -21,7 +22,7 @@ var _ HandledHooks = Hooks{}

func (k Keeper) Hooks() Hooks { return Hooks{k} }

func (h Hooks) AfterBTCRollBack(ctx context.Context, _ *ltypes.BTCHeaderInfo) {
func (h Hooks) AfterBTCRollBack(ctx context.Context, _, _ *ltypes.BTCHeaderInfo) {
h.k.setBtcLightClientUpdated(ctx)
}

Expand Down
5 changes: 4 additions & 1 deletion x/btclightclient/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,10 @@ maintained Bitcoin best chain:
// of the current mainchain to which we are rolling back to.
// In other words, there is one rollback event emitted per re-org, to the
// greatest common ancestor of the old and the new fork.
message EventBTCRollBack { BTCHeaderInfo header = 1; }
message EventBTCRollBack {
BTCHeaderInfo header = 1;
BTCHeaderInfo rollback_from = 2;
}
// EventBTCRollForward is emitted on Msg/InsertHeader
// The header included in the event is the one the main chain is extended with.
Expand Down
5 changes: 3 additions & 2 deletions x/btclightclient/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"

"github.com/babylonlabs-io/babylon/x/btclightclient/types"
)

Expand All @@ -16,9 +17,9 @@ func (k Keeper) AfterBTCHeaderInserted(ctx context.Context, headerInfo *types.BT
}

// AfterBTCRollBack - call hook if registered
func (k Keeper) AfterBTCRollBack(ctx context.Context, headerInfo *types.BTCHeaderInfo) {
func (k Keeper) AfterBTCRollBack(ctx context.Context, rollbackFrom, rollbackTo *types.BTCHeaderInfo) {
if k.hooks != nil {
k.hooks.AfterBTCRollBack(ctx, headerInfo)
k.hooks.AfterBTCRollBack(ctx, rollbackFrom, rollbackTo)
}
}

Expand Down
4 changes: 3 additions & 1 deletion x/btclightclient/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ func (k Keeper) triggerEventAndHandleHooksHandler() func(ctx context.Context, s
return func(ctx context.Context, s headersState, result *types.InsertResult) error {
// if we have rollback, first delete all headers up to the rollback point
if result.RollbackInfo != nil {
// gets the tip prior to rollback and delete
lastTip := s.GetTip()
// roll back to the height
s.rollBackHeadersUpTo(result.RollbackInfo.HeaderToRollbackTo.Height)
// trigger rollback event
k.triggerRollBack(ctx, result.RollbackInfo.HeaderToRollbackTo)
k.triggerRollBack(ctx, lastTip, result.RollbackInfo.HeaderToRollbackTo)
}

for _, header := range result.HeadersToInsert {
Expand Down
36 changes: 33 additions & 3 deletions x/btclightclient/keeper/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

"github.com/babylonlabs-io/babylon/x/btclightclient/types"
)
Expand All @@ -13,11 +14,17 @@ func (k Keeper) triggerHeaderInserted(ctx context.Context, headerInfo *types.BTC
k.emitTypedEventWithLog(ctx, &types.EventBTCHeaderInserted{Header: headerInfo})
}

func (k Keeper) triggerRollBack(ctx context.Context, headerInfo *types.BTCHeaderInfo) {
// triggerRollBack calls the hook and emits an event, the rollbackFrom is the latest tip
// prior rollbackTo best block is sent
func (k Keeper) triggerRollBack(ctx context.Context, rollbackFrom, rollbackTo *types.BTCHeaderInfo) {
// Safety check variables
if err := CheckRollBackInvariants(rollbackFrom, rollbackTo); err != nil {
panic(err)
}
// Trigger AfterBTCRollBack hook
k.AfterBTCRollBack(ctx, headerInfo)
k.AfterBTCRollBack(ctx, rollbackFrom, rollbackTo)
// Emit BTCRollBack event
k.emitTypedEventWithLog(ctx, &types.EventBTCRollBack{Header: headerInfo})
k.emitTypedEventWithLog(ctx, &types.EventBTCRollBack{Header: rollbackTo, RollbackFrom: rollbackFrom})
}

func (k Keeper) triggerRollForward(ctx context.Context, headerInfo *types.BTCHeaderInfo) {
Expand All @@ -26,3 +33,26 @@ func (k Keeper) triggerRollForward(ctx context.Context, headerInfo *types.BTCHea
// Emit BTCRollForward event
k.emitTypedEventWithLog(ctx, &types.EventBTCRollForward{Header: headerInfo})
}

// CheckRollBackInvariants validates that the values being called to trigger rollback
// are expected, if return an error it is probably an programming error.
func CheckRollBackInvariants(rollbackFrom, rollbackTo *types.BTCHeaderInfo) error {
if rollbackFrom == nil {
return fmt.Errorf("Call BTC rollback without tip")
}

if rollbackTo == nil {
return fmt.Errorf("Call BTC rollback without rollbackTo")
}

// should verify that the BTC height it is rolling back is lower than the latest tip
if rollbackTo.Height >= rollbackFrom.Height {
return fmt.Errorf(
"BTC rollback with rollback 'To' higher or equal than 'From'\n%s\n%s",
fmt.Sprintf("'From' -> %d - %s", rollbackFrom.Height, rollbackFrom.Hash.MarshalHex()),
fmt.Sprintf("'To' -> %d - %s", rollbackTo.Height, rollbackTo.Hash.MarshalHex()),
)
}

return nil
}
106 changes: 106 additions & 0 deletions x/btclightclient/keeper/triggers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package keeper_test

import (
"fmt"
"math/rand"
"testing"
"time"

"github.com/babylonlabs-io/babylon/testutil/datagen"
"github.com/babylonlabs-io/babylon/x/btclightclient/keeper"
"github.com/babylonlabs-io/babylon/x/btclightclient/types"
"github.com/test-go/testify/require"
)

func TestCheckRollBackInvariants(t *testing.T) {
t.Parallel()
r := rand.New(rand.NewSource(time.Now().Unix()))

randHeaderFrom := datagen.GenRandomBTCHeaderInfo(r)
randHeaderTo := datagen.GenRandomBTCHeaderInfo(r)
tcs := []struct {
title string
rollbackFrom *types.BTCHeaderInfo
rollbackTo *types.BTCHeaderInfo
expErr error
}{
{
"No rollback 'from'",
nil,
datagen.GenRandomBTCHeaderInfo(r),
fmt.Errorf("Call BTC rollback without tip"),
},
{
"No rollback 'to'",
datagen.GenRandomBTCHeaderInfo(r),
nil,
fmt.Errorf("Call BTC rollback without rollbackTo"),
},
{
"Rollback 'from' height > 'to' height",
&types.BTCHeaderInfo{
Height: 10,
Hash: randHeaderFrom.Hash,
},
&types.BTCHeaderInfo{
Height: 12,
Hash: randHeaderTo.Hash,
},
fmt.Errorf(
"BTC rollback with rollback 'To' higher or equal than 'From'\n%s\n%s",
fmt.Sprintf("'From' -> %d - %s", 10, randHeaderFrom.Hash.MarshalHex()),
fmt.Sprintf("'To' -> %d - %s", 12, randHeaderTo.Hash.MarshalHex()),
),
},
{
"Rollback 'from' height == 'to' height",
&types.BTCHeaderInfo{
Height: 18,
Hash: randHeaderFrom.Hash,
},
&types.BTCHeaderInfo{
Height: 18,
Hash: randHeaderTo.Hash,
},
fmt.Errorf(
"BTC rollback with rollback 'To' higher or equal than 'From'\n%s\n%s",
fmt.Sprintf("'From' -> %d - %s", 18, randHeaderFrom.Hash.MarshalHex()),
fmt.Sprintf("'To' -> %d - %s", 18, randHeaderTo.Hash.MarshalHex()),
),
},
{
"Rollback to correct height",
&types.BTCHeaderInfo{
Height: 15,
},
&types.BTCHeaderInfo{
Height: 12,
},
nil,
},
{
"Rollback to very large height",
&types.BTCHeaderInfo{
Height: 15000,
},
&types.BTCHeaderInfo{
Height: 12,
},
nil,
},
}

for _, tc := range tcs {
t.Run(tc.title, func(t *testing.T) {
t.Parallel()

actErr := keeper.CheckRollBackInvariants(tc.rollbackFrom, tc.rollbackTo)
if tc.expErr != nil {
require.EqualError(t, actErr, tc.expErr.Error())
return
}

require.NoError(t, actErr)
})
}
}
4 changes: 2 additions & 2 deletions x/btclightclient/keeper/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func (m *MockHooks) AfterBTCRollForward(_ context.Context, headerInfo *types.BTC
m.AfterBTCRollForwardStore = append(m.AfterBTCRollForwardStore, headerInfo)
}

func (m *MockHooks) AfterBTCRollBack(_ context.Context, headerInfo *types.BTCHeaderInfo) {
m.AfterBTCRollBackStore = append(m.AfterBTCRollBackStore, headerInfo)
func (m *MockHooks) AfterBTCRollBack(_ context.Context, _, rollbackTo *types.BTCHeaderInfo) {
m.AfterBTCRollBackStore = append(m.AfterBTCRollBackStore, rollbackTo)
}

func (m *MockHooks) AfterBTCHeaderInserted(_ context.Context, headerInfo *types.BTCHeaderInfo) {
Expand Down
Loading

0 comments on commit dbcc7c4

Please sign in to comment.