Skip to content

Commit

Permalink
chore: Not block unjailing (#195)
Browse files Browse the repository at this point in the history
Closes #122 by introducing a separate event loop for unjailing requests
  • Loading branch information
gitferry authored Dec 5, 2024
1 parent bdf768e commit a358d7b
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* [#184](https://github.com/babylonlabs-io/finality-provider/pull/184) eots manager sign record store
* [#189](https://github.com/babylonlabs-io/finality-provider/pull/189) Remove `fpd register-finality-provider` cmd
* [#190](https://github.com/babylonlabs-io/finality-provider/pull/190) Benchmark pub rand
* [#195](https://github.com/babylonlabs-io/finality-provider/pull/195) Not block unjailing

### Bug Fixes

Expand Down
56 changes: 32 additions & 24 deletions finality-provider/service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type FinalityProviderApp struct {
metrics *metrics.FpMetrics

createFinalityProviderRequestChan chan *CreateFinalityProviderRequest
unjailFinalityProviderRequestChan chan *UnjailFinalityProviderRequest
criticalErrChan chan *CriticalError
}

Expand Down Expand Up @@ -114,6 +115,7 @@ func NewFinalityProviderApp(
eotsManager: em,
metrics: fpMetrics,
quit: make(chan struct{}),
unjailFinalityProviderRequestChan: make(chan *UnjailFinalityProviderRequest),
createFinalityProviderRequestChan: make(chan *CreateFinalityProviderRequest),
criticalErrChan: make(chan *CriticalError),
}, nil
Expand Down Expand Up @@ -251,12 +253,13 @@ func (app *FinalityProviderApp) Start() error {
app.startOnce.Do(func() {
app.logger.Info("Starting FinalityProviderApp")

app.wg.Add(5)
app.wg.Add(6)
go app.syncChainFpStatusLoop()
go app.registrationLoop()
go app.metricsUpdateLoop()
go app.monitorCriticalErr()
go app.monitorStatusUpdate()
go app.registrationLoop()
go app.unjailFpLoop()
})

return startErr
Expand Down Expand Up @@ -374,34 +377,39 @@ func (app *FinalityProviderApp) CreateFinalityProvider(
}

// UnjailFinalityProvider sends a transaction to unjail a finality-provider
func (app *FinalityProviderApp) UnjailFinalityProvider(fpPk *bbntypes.BIP340PubKey) (string, error) {
_, err := app.fps.GetFinalityProvider(fpPk.MustToBTCPK())
if err != nil {
return "", fmt.Errorf("failed to get finality provider from db: %w", err)
func (app *FinalityProviderApp) UnjailFinalityProvider(fpPk *bbntypes.BIP340PubKey) (*UnjailFinalityProviderResponse, error) {
// send request to the loop to avoid blocking the main thread
request := &UnjailFinalityProviderRequest{
btcPubKey: fpPk,
errResponse: make(chan error, 1),
successResponse: make(chan *UnjailFinalityProviderResponse, 1),
}

// Send unjail transaction
res, err := app.cc.UnjailFinalityProvider(fpPk.MustToBTCPK())
if err != nil {
return "", fmt.Errorf("failed to send unjail transaction: %w", err)
}
app.unjailFinalityProviderRequestChan <- request

// Update finality-provider status in the local store
// set it to INACTIVE for now and it will be updated to
// ACTIVE if the fp has voting power
err = app.fps.SetFpStatus(fpPk.MustToBTCPK(), proto.FinalityProviderStatus_INACTIVE)
if err != nil {
return "", fmt.Errorf("failed to update finality-provider status after unjailing: %w", err)
}
select {
case err := <-request.errResponse:
return nil, err
case successResponse := <-request.successResponse:
_, err := app.fps.GetFinalityProvider(fpPk.MustToBTCPK())
if err != nil {
return nil, fmt.Errorf("failed to get finality provider from db: %w", err)
}

app.metrics.RecordFpStatus(fpPk.MarshalHex(), proto.FinalityProviderStatus_INACTIVE)
// Update finality-provider status in the local store
// set it to INACTIVE for now and it will be updated to
// ACTIVE if the fp has voting power
err = app.fps.SetFpStatus(fpPk.MustToBTCPK(), proto.FinalityProviderStatus_INACTIVE)
if err != nil {
return nil, fmt.Errorf("failed to update finality-provider status after unjailing: %w", err)
}

app.logger.Info("successfully unjailed finality-provider",
zap.String("btc_pk", fpPk.MarshalHex()),
zap.String("txHash", res.TxHash),
)
app.metrics.RecordFpStatus(fpPk.MarshalHex(), proto.FinalityProviderStatus_INACTIVE)

return res.TxHash, nil
return successResponse, nil
case <-app.quit:
return nil, fmt.Errorf("finality-provider app is shutting down")
}
}

func (app *FinalityProviderApp) CreatePop(fpAddress sdk.AccAddress, fpPk *bbntypes.BIP340PubKey, passphrase string) (*bstypes.ProofOfPossessionBTC, error) {
Expand Down
6 changes: 3 additions & 3 deletions finality-provider/service/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func FuzzUnjailFinalityProvider(f *testing.F) {
// set voting power to be positive so that the fp should eventually become ACTIVE
mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(0), nil).AnyTimes()
mockClientController.EXPECT().QueryActivatedHeight().Return(uint64(1), nil).AnyTimes()
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, false, nil).AnyTimes()
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, true, nil).AnyTimes()
mockClientController.EXPECT().QueryFinalityProviderHighestVotedHeight(gomock.Any()).Return(uint64(0), nil).AnyTimes()

// Create fp app
Expand All @@ -223,9 +223,9 @@ func FuzzUnjailFinalityProvider(f *testing.F) {

expectedTxHash := datagen.GenRandomHexStr(r, 32)
mockClientController.EXPECT().UnjailFinalityProvider(fpPk.MustToBTCPK()).Return(&types.TxResponse{TxHash: expectedTxHash}, nil)
txHash, err := app.UnjailFinalityProvider(fpPk)
res, err := app.UnjailFinalityProvider(fpPk)
require.NoError(t, err)
require.Equal(t, expectedTxHash, txHash)
require.Equal(t, expectedTxHash, res.TxHash)
fpInfo, err := app.GetFinalityProviderInfo(fpPk)
require.NoError(t, err)
require.Equal(t, proto.FinalityProviderStatus_INACTIVE.String(), fpInfo.GetStatus())
Expand Down
86 changes: 86 additions & 0 deletions finality-provider/service/event_loops.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,48 @@ package service

import (
"errors"
"fmt"
"time"

sdkmath "cosmossdk.io/math"
bbntypes "github.com/babylonlabs-io/babylon/types"
btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"go.uber.org/zap"

"github.com/babylonlabs-io/finality-provider/finality-provider/proto"
)

type CreateFinalityProviderRequest struct {
fpAddr sdk.AccAddress
btcPubKey *bbntypes.BIP340PubKey
pop *btcstakingtypes.ProofOfPossessionBTC
description *stakingtypes.Description
commission *sdkmath.LegacyDec
errResponse chan error
successResponse chan *RegisterFinalityProviderResponse
}

type RegisterFinalityProviderResponse struct {
txHash string
}

type CreateFinalityProviderResult struct {
FpInfo *proto.FinalityProviderInfo
TxHash string
}

type UnjailFinalityProviderRequest struct {
btcPubKey *bbntypes.BIP340PubKey
errResponse chan error
successResponse chan *UnjailFinalityProviderResponse
}

type UnjailFinalityProviderResponse struct {
TxHash string
}

// monitorStatusUpdate periodically check the status of the running finality provider and update
// it accordingly. We update the status by querying the latest voting power and the slashed_height.
// In particular, we perform the following status transitions (REGISTERED, ACTIVE, INACTIVE, SLASHED):
Expand Down Expand Up @@ -201,6 +236,57 @@ func (app *FinalityProviderApp) registrationLoop() {
}
}

// event loop for unjailing fp
func (app *FinalityProviderApp) unjailFpLoop() {
defer app.wg.Done()
for {
select {
case req := <-app.unjailFinalityProviderRequestChan:
pkHex := req.btcPubKey.MarshalHex()
isSlashed, isJailed, err := app.cc.QueryFinalityProviderSlashedOrJailed(req.btcPubKey.MustToBTCPK())
if err != nil {
req.errResponse <- fmt.Errorf("failed to query jailing status of the finality provider %s: %w", pkHex, err)
continue
}
if isSlashed {
req.errResponse <- fmt.Errorf("the finality provider %s is already slashed", pkHex)
continue
}
if !isJailed {
req.errResponse <- fmt.Errorf("the finality provider %s is not jailed", pkHex)
continue
}

res, err := app.cc.UnjailFinalityProvider(
req.btcPubKey.MustToBTCPK(),
)

if err != nil {
app.logger.Error(
"failed to unjail finality-provider",
zap.String("pk", req.btcPubKey.MarshalHex()),
zap.Error(err),
)
req.errResponse <- err
continue
}

app.logger.Info(
"successfully unjailed finality-provider on babylon",
zap.String("btc_pk", req.btcPubKey.MarshalHex()),
zap.String("txHash", res.TxHash),
)

req.successResponse <- &UnjailFinalityProviderResponse{
TxHash: res.TxHash,
}
case <-app.quit:
app.logger.Info("exiting unjailing fp loop")
return
}
}
}

// event loop for metrics update
func (app *FinalityProviderApp) metricsUpdateLoop() {
defer app.wg.Done()
Expand Down
2 changes: 1 addition & 1 deletion finality-provider/service/fp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (fp *FinalityProviderInstance) Stop() error {
close(fp.quit)
fp.wg.Wait()

fp.logger.Info("the finality-provider instance %s is successfully stopped", zap.String("pk", fp.GetBtcPkHex()))
fp.logger.Info("the finality-provider instance is successfully stopped", zap.String("pk", fp.GetBtcPkHex()))

return nil
}
Expand Down
23 changes: 0 additions & 23 deletions finality-provider/service/fp_store_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,14 @@ package service
import (
"sync"

sdkmath "cosmossdk.io/math"
bbntypes "github.com/babylonlabs-io/babylon/types"
btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
"github.com/btcsuite/btcd/btcec/v2"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"go.uber.org/zap"

"github.com/babylonlabs-io/finality-provider/finality-provider/proto"
"github.com/babylonlabs-io/finality-provider/finality-provider/store"
)

type CreateFinalityProviderRequest struct {
fpAddr sdk.AccAddress
btcPubKey *bbntypes.BIP340PubKey
pop *btcstakingtypes.ProofOfPossessionBTC
description *stakingtypes.Description
commission *sdkmath.LegacyDec
errResponse chan error
successResponse chan *RegisterFinalityProviderResponse
}

type RegisterFinalityProviderResponse struct {
txHash string
}

type CreateFinalityProviderResult struct {
FpInfo *proto.FinalityProviderInfo
TxHash string
}

type fpState struct {
mu sync.Mutex
fp *store.StoredFinalityProvider
Expand Down
4 changes: 2 additions & 2 deletions finality-provider/service/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (r *rpcServer) UnjailFinalityProvider(_ context.Context, req *proto.UnjailF
return nil, err
}

txHash, err := r.app.UnjailFinalityProvider(fpPk)
res, err := r.app.UnjailFinalityProvider(fpPk)
if err != nil {
return nil, fmt.Errorf("failed to unjail the finality-provider: %w", err)
}
Expand All @@ -211,7 +211,7 @@ func (r *rpcServer) UnjailFinalityProvider(_ context.Context, req *proto.UnjailF
return nil, fmt.Errorf("failed to start the finality provider instance after unjailing: %w", err)
}

return &proto.UnjailFinalityProviderResponse{TxHash: txHash}, nil
return &proto.UnjailFinalityProviderResponse{TxHash: res.TxHash}, nil
}

// QueryFinalityProvider queries the information of the finality-provider
Expand Down

0 comments on commit a358d7b

Please sign in to comment.