From 01040a2422999eb48caa410f93bae1c2a8ea9421 Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Tue, 8 Oct 2024 16:01:09 +0800 Subject: [PATCH] remove support for multiple fps running --- finality-provider/cmd/fpd/daemon/start.go | 32 +- finality-provider/config/config.go | 3 - finality-provider/service/app.go | 22 +- finality-provider/service/app_test.go | 4 +- finality-provider/service/fp_instance.go | 7 +- finality-provider/service/fp_instance_test.go | 3 +- finality-provider/service/fp_manager.go | 301 +++++++----------- finality-provider/service/fp_manager_test.go | 3 +- finality-provider/service/rpcserver.go | 8 +- finality-provider/store/storedfp.go | 6 +- itest/e2e_test.go | 16 +- itest/test_manager.go | 125 ++++---- 12 files changed, 226 insertions(+), 304 deletions(-) diff --git a/finality-provider/cmd/fpd/daemon/start.go b/finality-provider/cmd/fpd/daemon/start.go index 0a8c92db..3f883758 100644 --- a/finality-provider/cmd/fpd/daemon/start.go +++ b/finality-provider/cmd/fpd/daemon/start.go @@ -120,35 +120,31 @@ func startApp( fpApp *service.FinalityProviderApp, fpPkStr, passphrase string, ) error { - // only start the app without starting any finality-provider instance - // as there might be no finality-provider registered yet + // only start the app without starting any finality provider instance + // this is needed for new finality provider registration or unjailing + // finality providers if err := fpApp.Start(); err != nil { - return fmt.Errorf("failed to start the finality-provider app: %w", err) + return fmt.Errorf("failed to start the finality provider app: %w", err) } - if fpPkStr != "" { - // start the finality-provider instance with the given public key - fpPk, err := types.NewBIP340PubKeyFromHex(fpPkStr) - if err != nil { - return fmt.Errorf("invalid finality-provider public key %s: %w", fpPkStr, err) - } + // no fp instance will be started if public key is not specified + if fpPkStr == "" { + return nil + } - if err := fpApp.StartHandlingFinalityProvider(fpPk, passphrase); err != nil { - if errors.Is(err, service.ErrFinalityProviderJailed) { - fpApp.Logger().Error("failed to start finality provider", zap.Error(err)) - // do not return error as we still want the service to start - return nil - } - return fmt.Errorf("failed to start the finality-provider instance %s: %w", fpPkStr, err) - } + // start the finality-provider instance with the given public key + fpPk, err := types.NewBIP340PubKeyFromHex(fpPkStr) + if err != nil { + return fmt.Errorf("invalid finality provider public key %s: %w", fpPkStr, err) } - if err := fpApp.StartHandlingAll(); err != nil { + if err := fpApp.StartHandlingFinalityProvider(fpPk, passphrase); err != nil { if errors.Is(err, service.ErrFinalityProviderJailed) { fpApp.Logger().Error("failed to start finality provider", zap.Error(err)) // do not return error as we still want the service to start return nil } + return fmt.Errorf("failed to start the finality-provider instance %s: %w", fpPkStr, err) } return nil diff --git a/finality-provider/config/config.go b/finality-provider/config/config.go index d8a8b423..3c4655fa 100644 --- a/finality-provider/config/config.go +++ b/finality-provider/config/config.go @@ -38,7 +38,6 @@ const ( defaultMaxSubmissionRetries = 20 defaultBitcoinNetwork = "signet" defaultDataDirname = "data" - defaultMaxNumFinalityProviders = 3 ) var ( @@ -69,7 +68,6 @@ type Config struct { FastSyncLimit uint64 `long:"fastsynclimit" description:"The maximum number of blocks to catch up for each fast sync"` FastSyncGap uint64 `long:"fastsyncgap" description:"The block gap that will trigger the fast sync"` EOTSManagerAddress string `long:"eotsmanageraddress" description:"The address of the remote EOTS manager; Empty if the EOTS manager is running locally"` - MaxNumFinalityProviders uint32 `long:"maxnumfinalityproviders" description:"The maximum number of finality-provider instances running concurrently within the daemon"` SyncFpStatusInterval time.Duration `long:"syncfpstatusinterval" description:"The duration of time that it should sync FP status with the client blockchain"` BitcoinNetwork string `long:"bitcoinnetwork" description:"Bitcoin network to run on" choise:"mainnet" choice:"regtest" choice:"testnet" choice:"simnet" choice:"signet"` @@ -112,7 +110,6 @@ func DefaultConfigWithHome(homePath string) Config { BTCNetParams: defaultBTCNetParams, EOTSManagerAddress: defaultEOTSManagerAddress, RpcListener: DefaultRpcListener, - MaxNumFinalityProviders: defaultMaxNumFinalityProviders, Metrics: metrics.DefaultFpConfig(), SyncFpStatusInterval: defaultSyncFpStatusInterval, } diff --git a/finality-provider/service/app.go b/finality-provider/service/app.go index 01d3e3be..0b12ffb8 100644 --- a/finality-provider/service/app.go +++ b/finality-provider/service/app.go @@ -151,10 +151,6 @@ func (app *FinalityProviderApp) Logger() *zap.Logger { return app.logger } -func (app *FinalityProviderApp) ListFinalityProviderInstances() []*FinalityProviderInstance { - return app.fpManager.ListFinalityProviderInstances() -} - func (app *FinalityProviderApp) ListAllFinalityProvidersInfo() ([]*proto.FinalityProviderInfo, error) { return app.fpManager.AllFinalityProviders() } @@ -164,8 +160,8 @@ func (app *FinalityProviderApp) GetFinalityProviderInfo(fpPk *bbntypes.BIP340Pub } // GetFinalityProviderInstance returns the finality-provider instance with the given Babylon public key -func (app *FinalityProviderApp) GetFinalityProviderInstance(fpPk *bbntypes.BIP340PubKey) (*FinalityProviderInstance, error) { - return app.fpManager.GetFinalityProviderInstance(fpPk) +func (app *FinalityProviderApp) GetFinalityProviderInstance() (*FinalityProviderInstance, error) { + return app.fpManager.GetFinalityProviderInstance() } func (app *FinalityProviderApp) RegisterFinalityProvider(fpPkStr string) (*RegisterFinalityProviderResponse, error) { @@ -220,16 +216,12 @@ func (app *FinalityProviderApp) RegisterFinalityProvider(fpPkStr string) (*Regis } } -// StartHandlingFinalityProvider starts a finality-provider instance with the given Babylon public key +// StartHandlingFinalityProvider starts a finality provider instance with the given EOTS public key // Note: this should be called right after the finality-provider is registered func (app *FinalityProviderApp) StartHandlingFinalityProvider(fpPk *bbntypes.BIP340PubKey, passphrase string) error { return app.fpManager.StartFinalityProvider(fpPk, passphrase) } -func (app *FinalityProviderApp) StartHandlingAll() error { - return app.fpManager.StartAll() -} - // NOTE: this is not safe in production, so only used for testing purpose func (app *FinalityProviderApp) getFpPrivKey(fpPk []byte) (*btcec.PrivateKey, error) { record, err := app.eotsManager.KeyRecord(fpPk, "") @@ -329,9 +321,11 @@ func (app *FinalityProviderApp) Stop() error { app.wg.Wait() app.logger.Debug("Stopping finality providers") - if err := app.fpManager.Stop(); err != nil { - stopErr = err - return + if app.fpManager.isStarted.Load() { + if err := app.fpManager.Stop(); err != nil { + stopErr = err + return + } } app.logger.Debug("Stopping EOTS manager") diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index d4ccad71..edb3fa1c 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -133,7 +133,7 @@ func FuzzRegisterFinalityProvider(f *testing.F) { err = app.StartHandlingFinalityProvider(fp.GetBIP340BTCPK(), passphrase) require.NoError(t, err) - fpAfterReg, err := app.GetFinalityProviderInstance(fp.GetBIP340BTCPK()) + fpAfterReg, err := app.GetFinalityProviderInstance() require.NoError(t, err) require.Equal(t, proto.FinalityProviderStatus_REGISTERED, fpAfterReg.GetStoreFinalityProvider().Status) @@ -210,7 +210,7 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { if noVotingPowerTable { expectedStatus = proto.FinalityProviderStatus_REGISTERED } - fpInstance, err := app.GetFinalityProviderInstance(fpPk) + fpInstance, err := app.GetFinalityProviderInstance() if err != nil { return false } diff --git a/finality-provider/service/fp_instance.go b/finality-provider/service/fp_instance.go index d643ee8a..b13445c6 100644 --- a/finality-provider/service/fp_instance.go +++ b/finality-provider/service/fp_instance.go @@ -67,12 +67,11 @@ func NewFinalityProviderInstance( ) (*FinalityProviderInstance, error) { sfp, err := s.GetFinalityProvider(fpPk.MustToBTCPK()) if err != nil { - return nil, fmt.Errorf("failed to retrive the finality-provider %s from DB: %w", fpPk.MarshalHex(), err) + return nil, fmt.Errorf("failed to retrive the finality provider %s from DB: %w", fpPk.MarshalHex(), err) } - // ensure the finality-provider has been registered - if sfp.Status < proto.FinalityProviderStatus_REGISTERED { - return nil, fmt.Errorf("the finality-provider %s has not been registered", sfp.KeyName) + if !sfp.ShouldStart() { + return nil, fmt.Errorf("the finality provider instance cannot be initiated with status %s", sfp.Status.String()) } return &FinalityProviderInstance{ diff --git a/finality-provider/service/fp_instance_test.go b/finality-provider/service/fp_instance_test.go index 75cb57b7..dc2cbf6d 100644 --- a/finality-provider/service/fp_instance_test.go +++ b/finality-provider/service/fp_instance_test.go @@ -12,6 +12,7 @@ import ( "github.com/babylonlabs-io/babylon/testutil/datagen" ftypes "github.com/babylonlabs-io/babylon/x/finality/types" + "github.com/babylonlabs-io/finality-provider/clientcontroller" "github.com/babylonlabs-io/finality-provider/eotsmanager" eotscfg "github.com/babylonlabs-io/finality-provider/eotsmanager/config" @@ -120,8 +121,6 @@ func startFinalityProviderAppWithRegisteredFp(t *testing.T, r *rand.Rand, cc cli require.NoError(t, err) err = app.Start() require.NoError(t, err) - err = app.StartHandlingAll() - require.NoError(t, err) // create registered finality-provider fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath, nil) diff --git a/finality-provider/service/fp_manager.go b/finality-provider/service/fp_manager.go index 55c87e58..2acc2b67 100644 --- a/finality-provider/service/fp_manager.go +++ b/finality-provider/service/fp_manager.go @@ -31,15 +31,16 @@ func (ce *CriticalError) Error() string { return fmt.Sprintf("critical err on finality-provider %s: %s", ce.fpBtcPk.MarshalHex(), ce.err.Error()) } +// FinalityProviderManager is responsible to initiate and start the given finality +// provider instance, monitor its running status type FinalityProviderManager struct { isStarted *atomic.Bool - // mutex to acess map of fp instances (fpis) + // mutex to acess map of fp instances (fpIns) mu sync.Mutex wg sync.WaitGroup - // running finality-provider instances map keyed by the hex string of the BTC public key - fpis map[string]*FinalityProviderInstance + fpIns *FinalityProviderInstance // needed for initiating finality-provider instances fps *store.FinalityProviderStore @@ -66,7 +67,6 @@ func NewFinalityProviderManager( logger *zap.Logger, ) (*FinalityProviderManager, error) { return &FinalityProviderManager{ - fpis: make(map[string]*FinalityProviderInstance), criticalErrChan: make(chan *CriticalError), isStarted: atomic.NewBool(false), fps: fps, @@ -88,27 +88,33 @@ func (fpm *FinalityProviderManager) monitorCriticalErr() { defer fpm.wg.Done() var criticalErr *CriticalError - for { + + exitLoop := false + for !exitLoop { select { case criticalErr = <-fpm.criticalErrChan: - fpi, err := fpm.GetFinalityProviderInstance(criticalErr.fpBtcPk) + fpi, err := fpm.GetFinalityProviderInstance() if err != nil { fpm.logger.Debug("the finality-provider instance is already shutdown", zap.String("pk", criticalErr.fpBtcPk.MarshalHex())) + + exitLoop = true continue } - // cannot use error.Is because the unwrapped error - // is not the expected error type if errors.Is(criticalErr.err, ErrFinalityProviderSlashed) { fpm.setFinalityProviderSlashed(fpi) fpm.logger.Debug("the finality-provider has been slashed", zap.String("pk", criticalErr.fpBtcPk.MarshalHex())) + + exitLoop = true continue } if errors.Is(criticalErr.err, ErrFinalityProviderJailed) { fpm.setFinalityProviderJailed(fpi) fpm.logger.Debug("the finality-provider has been jailed", zap.String("pk", criticalErr.fpBtcPk.MarshalHex())) + + exitLoop = true continue } fpm.logger.Fatal(instanceTerminatingMsg, @@ -117,6 +123,10 @@ func (fpm *FinalityProviderManager) monitorCriticalErr() { return } } + + if err := fpm.Stop(); err != nil { + fpm.logger.Fatal("failed to stop the finality provider manager", zap.Error(err)) + } } // monitorStatusUpdate periodically check the status of each managed finality providers and update @@ -140,75 +150,77 @@ func (fpm *FinalityProviderManager) monitorStatusUpdate() { for { select { case <-statusUpdateTicker.C: + fpi := fpm.fpIns + if fpi == nil { + continue + } + latestBlock, err := fpm.getLatestBlockWithRetry() if err != nil { fpm.logger.Debug("failed to get the latest block", zap.Error(err)) continue } - fpis := fpm.ListFinalityProviderInstances() - for _, fpi := range fpis { - oldStatus := fpi.GetStatus() - power, err := fpi.GetVotingPowerWithRetry(latestBlock.Height) - if err != nil { - fpm.logger.Debug( - "failed to get the voting power", - zap.String("fp_btc_pk", fpi.GetBtcPkHex()), - zap.Uint64("height", latestBlock.Height), - zap.Error(err), - ) - continue - } - // power > 0 (slashed_height must > 0), set status to ACTIVE - if power > 0 { - if oldStatus != proto.FinalityProviderStatus_ACTIVE { - fpi.MustSetStatus(proto.FinalityProviderStatus_ACTIVE) - fpm.logger.Debug( - "the finality-provider status is changed to ACTIVE", - zap.String("fp_btc_pk", fpi.GetBtcPkHex()), - zap.String("old_status", oldStatus.String()), - zap.Uint64("power", power), - ) - } - continue - } - slashed, jailed, err := fpi.GetFinalityProviderSlashedOrJailedWithRetry() - if err != nil { - fpm.logger.Debug( - "failed to get the slashed or jailed status", - zap.String("fp_btc_pk", fpi.GetBtcPkHex()), - zap.Error(err), - ) - continue - } - // power == 0 and slashed == true, set status to SLASHED, stop, and remove the finality-provider instance - if slashed { - fpm.setFinalityProviderSlashed(fpi) - fpm.logger.Warn( - "the finality-provider is slashed", - zap.String("fp_btc_pk", fpi.GetBtcPkHex()), - zap.String("old_status", oldStatus.String()), - ) - continue - } - // power == 0 and jailed == true, set status to JAILED, stop, and remove the finality-provider instance - if jailed { - fpm.setFinalityProviderJailed(fpi) - fpm.logger.Warn( - "the finality-provider is jailed", - zap.String("fp_btc_pk", fpi.GetBtcPkHex()), - zap.String("old_status", oldStatus.String()), - ) - continue - } - // power == 0 and slashed_height == 0, change to INACTIVE if the current status is ACTIVE - if oldStatus == proto.FinalityProviderStatus_ACTIVE { - fpi.MustSetStatus(proto.FinalityProviderStatus_INACTIVE) + oldStatus := fpi.GetStatus() + power, err := fpi.GetVotingPowerWithRetry(latestBlock.Height) + if err != nil { + fpm.logger.Debug( + "failed to get the voting power", + zap.String("fp_btc_pk", fpi.GetBtcPkHex()), + zap.Uint64("height", latestBlock.Height), + zap.Error(err), + ) + continue + } + // power > 0 (slashed_height must > 0), set status to ACTIVE + if power > 0 { + if oldStatus != proto.FinalityProviderStatus_ACTIVE { + fpi.MustSetStatus(proto.FinalityProviderStatus_ACTIVE) fpm.logger.Debug( - "the finality-provider status is changed to INACTIVE", + "the finality-provider status is changed to ACTIVE", zap.String("fp_btc_pk", fpi.GetBtcPkHex()), zap.String("old_status", oldStatus.String()), + zap.Uint64("power", power), ) } + continue + } + slashed, jailed, err := fpi.GetFinalityProviderSlashedOrJailedWithRetry() + if err != nil { + fpm.logger.Debug( + "failed to get the slashed or jailed status", + zap.String("fp_btc_pk", fpi.GetBtcPkHex()), + zap.Error(err), + ) + continue + } + // power == 0 and slashed == true, set status to SLASHED, stop, and remove the finality-provider instance + if slashed { + fpm.setFinalityProviderSlashed(fpi) + fpm.logger.Warn( + "the finality-provider is slashed", + zap.String("fp_btc_pk", fpi.GetBtcPkHex()), + zap.String("old_status", oldStatus.String()), + ) + continue + } + // power == 0 and jailed == true, set status to JAILED, stop, and remove the finality-provider instance + if jailed { + fpm.setFinalityProviderJailed(fpi) + fpm.logger.Warn( + "the finality-provider is jailed", + zap.String("fp_btc_pk", fpi.GetBtcPkHex()), + zap.String("old_status", oldStatus.String()), + ) + continue + } + // power == 0 and slashed_height == 0, change to INACTIVE if the current status is ACTIVE + if oldStatus == proto.FinalityProviderStatus_ACTIVE { + fpi.MustSetStatus(proto.FinalityProviderStatus_INACTIVE) + fpm.logger.Debug( + "the finality-provider status is changed to INACTIVE", + zap.String("fp_btc_pk", fpi.GetBtcPkHex()), + zap.String("old_status", oldStatus.String()), + ) } case <-fpm.quit: return @@ -218,14 +230,14 @@ func (fpm *FinalityProviderManager) monitorStatusUpdate() { func (fpm *FinalityProviderManager) setFinalityProviderSlashed(fpi *FinalityProviderInstance) { fpi.MustSetStatus(proto.FinalityProviderStatus_SLASHED) - if err := fpm.removeFinalityProviderInstance(fpi.GetBtcPkBIP340()); err != nil { + if err := fpm.removeFinalityProviderInstance(); err != nil { panic(fmt.Errorf("failed to terminate a slashed finality-provider %s: %w", fpi.GetBtcPkHex(), err)) } } func (fpm *FinalityProviderManager) setFinalityProviderJailed(fpi *FinalityProviderInstance) { fpi.MustSetStatus(proto.FinalityProviderStatus_JAILED) - if err := fpm.removeFinalityProviderInstance(fpi.GetBtcPkBIP340()); err != nil { + if err := fpm.removeFinalityProviderInstance(); err != nil { panic(fmt.Errorf("failed to terminate a jailed finality-provider %s: %w", fpi.GetBtcPkHex(), err)) } } @@ -234,27 +246,9 @@ func (fpm *FinalityProviderManager) StartFinalityProvider(fpPk *bbntypes.BIP340P if !fpm.isStarted.Load() { fpm.isStarted.Store(true) - fpm.wg.Add(1) - go fpm.monitorCriticalErr() - - fpm.wg.Add(1) - go fpm.monitorStatusUpdate() - } - - if fpm.numOfRunningFinalityProviders() >= int(fpm.config.MaxNumFinalityProviders) { - return fmt.Errorf("reaching maximum number of running finality providers %v", fpm.config.MaxNumFinalityProviders) - } - - if err := fpm.addFinalityProviderInstance(fpPk, passphrase); err != nil { - return err - } - - return nil -} - -func (fpm *FinalityProviderManager) StartAll() error { - if !fpm.isStarted.Load() { - fpm.isStarted.Store(true) + if err := fpm.startFinalityProviderInstance(fpPk, passphrase); err != nil { + return err + } fpm.wg.Add(1) go fpm.monitorCriticalErr() @@ -263,26 +257,6 @@ func (fpm *FinalityProviderManager) StartAll() error { go fpm.monitorStatusUpdate() } - storedFps, err := fpm.fps.GetAllStoredFinalityProviders() - if err != nil { - return err - } - - for _, fp := range storedFps { - fpBtcPk := fp.GetBIP340BTCPK() - if !fp.ShouldStart() { - fpm.logger.Info( - "the finality provider cannot be started with status", - zap.String("eots-pk", fpBtcPk.MarshalHex()), - zap.String("status", fp.Status.String()), - ) - continue - } - if err := fpm.StartFinalityProvider(fpBtcPk, ""); err != nil { - return err - } - } - return nil } @@ -291,34 +265,25 @@ func (fpm *FinalityProviderManager) Stop() error { return fmt.Errorf("the finality-provider manager has already stopped") } - var stopErr error - for _, fpi := range fpm.fpis { - if !fpi.IsRunning() { - continue - } - if err := fpi.Stop(); err != nil { - stopErr = err - break - } - fpm.metrics.DecrementRunningFpGauge() - } - close(fpm.quit) fpm.wg.Wait() - return stopErr -} + if fpm.fpIns == nil { + return nil + } + if !fpm.fpIns.IsRunning() { + return nil + } -func (fpm *FinalityProviderManager) ListFinalityProviderInstances() []*FinalityProviderInstance { - fpm.mu.Lock() - defer fpm.mu.Unlock() + return fpm.fpIns.Stop() +} - fpisList := make([]*FinalityProviderInstance, 0, len(fpm.fpis)) - for _, fpi := range fpm.fpis { - fpisList = append(fpisList, fpi) +func (fpm *FinalityProviderManager) GetFinalityProviderInstance() (*FinalityProviderInstance, error) { + if fpm.fpIns == nil { + return nil, fmt.Errorf("finality provider does not exist") } - return fpisList + return fpm.fpIns, nil } func (fpm *FinalityProviderManager) AllFinalityProviders() ([]*proto.FinalityProviderInfo, error) { @@ -357,55 +322,35 @@ func (fpm *FinalityProviderManager) FinalityProviderInfo(fpPk *bbntypes.BIP340Pu } func (fpm *FinalityProviderManager) IsFinalityProviderRunning(fpPk *bbntypes.BIP340PubKey) bool { - fpm.mu.Lock() - defer fpm.mu.Unlock() - - _, exists := fpm.fpis[fpPk.MarshalHex()] - return exists -} - -func (fpm *FinalityProviderManager) GetFinalityProviderInstance(fpPk *bbntypes.BIP340PubKey) (*FinalityProviderInstance, error) { - fpm.mu.Lock() - defer fpm.mu.Unlock() + if fpm.fpIns == nil { + return false + } - keyHex := fpPk.MarshalHex() - v, exists := fpm.fpis[keyHex] - if !exists { - return nil, fmt.Errorf("cannot find the finality-provider instance with PK: %s", keyHex) + if fpm.fpIns.GetBtcPkHex() != fpPk.MarshalHex() { + return false } - return v, nil + return fpm.fpIns.IsRunning() } -func (fpm *FinalityProviderManager) removeFinalityProviderInstance(fpPk *bbntypes.BIP340PubKey) error { - fpm.mu.Lock() - defer fpm.mu.Unlock() - - keyHex := fpPk.MarshalHex() - fpi, exists := fpm.fpis[keyHex] - if !exists { - return fmt.Errorf("cannot find the finality-provider instance with PK: %s", keyHex) +func (fpm *FinalityProviderManager) removeFinalityProviderInstance() error { + fpi := fpm.fpIns + if fpi == nil { + return fmt.Errorf("the finality provider instance does not exist") } if fpi.IsRunning() { if err := fpi.Stop(); err != nil { - return fmt.Errorf("failed to stop the finality-provider instance %s", keyHex) + return fmt.Errorf("failed to stop the finality provider instance %s", fpi.GetBtcPkHex()) } } - delete(fpm.fpis, keyHex) - fpm.metrics.DecrementRunningFpGauge() - return nil -} + fpm.fpIns = nil -func (fpm *FinalityProviderManager) numOfRunningFinalityProviders() int { - fpm.mu.Lock() - defer fpm.mu.Unlock() - - return len(fpm.fpis) + return nil } -// addFinalityProviderInstance creates a finality-provider instance, starts it and adds it into the finality-provider manager -func (fpm *FinalityProviderManager) addFinalityProviderInstance( +// startFinalityProviderInstance creates a finality-provider instance, starts it and adds it into the finality-provider manager +func (fpm *FinalityProviderManager) startFinalityProviderInstance( pk *bbntypes.BIP340PubKey, passphrase string, ) error { @@ -413,23 +358,19 @@ func (fpm *FinalityProviderManager) addFinalityProviderInstance( defer fpm.mu.Unlock() pkHex := pk.MarshalHex() - if _, exists := fpm.fpis[pkHex]; exists { - return fmt.Errorf("finality-provider instance already exists") - } - - fpIns, err := NewFinalityProviderInstance(pk, fpm.config, fpm.fps, fpm.pubRandStore, fpm.cc, fpm.em, fpm.metrics, passphrase, fpm.criticalErrChan, fpm.logger) - if err != nil { - return fmt.Errorf("failed to create finality-provider %s instance: %w", pkHex, err) - } + if fpm.fpIns == nil { + fpIns, err := NewFinalityProviderInstance( + pk, fpm.config, fpm.fps, fpm.pubRandStore, fpm.cc, fpm.em, + fpm.metrics, passphrase, fpm.criticalErrChan, fpm.logger, + ) + if err != nil { + return fmt.Errorf("failed to create finality provider instance %s: %w", pkHex, err) + } - if err := fpIns.Start(); err != nil { - return fmt.Errorf("failed to start finality-provider %s instance: %w", pkHex, err) + fpm.fpIns = fpIns } - fpm.fpis[pkHex] = fpIns - fpm.metrics.IncrementRunningFpGauge() - - return nil + return fpm.fpIns.Start() } func (fpm *FinalityProviderManager) getLatestBlockWithRetry() (*types.BlockInfo, error) { diff --git a/finality-provider/service/fp_manager_test.go b/finality-provider/service/fp_manager_test.go index 71a02627..0f07caa7 100644 --- a/finality-provider/service/fp_manager_test.go +++ b/finality-provider/service/fp_manager_test.go @@ -76,7 +76,8 @@ func FuzzStatusUpdate(f *testing.F) { err := vm.StartFinalityProvider(fpPk, passphrase) require.NoError(t, err) - fpIns := vm.ListFinalityProviderInstances()[0] + fpIns, err := vm.GetFinalityProviderInstance() + require.NoError(t, err) // stop the finality-provider as we are testing static functionalities err = fpIns.Stop() require.NoError(t, err) diff --git a/finality-provider/service/rpcserver.go b/finality-provider/service/rpcserver.go index 3064000e..5ba774fb 100644 --- a/finality-provider/service/rpcserver.go +++ b/finality-provider/service/rpcserver.go @@ -146,11 +146,17 @@ func (r *rpcServer) AddFinalitySignature(ctx context.Context, req *proto.AddFina return nil, err } - fpi, err := r.app.GetFinalityProviderInstance(fpPk) + fpi, err := r.app.GetFinalityProviderInstance() if err != nil { return nil, err } + if fpi.GetBtcPkHex() != req.BtcPk { + return nil, fmt.Errorf( + "the finality provider running does not match the request, got: %s, expected: %s", + req.BtcPk, fpi.GetBtcPkHex()) + } + b := &types.BlockInfo{ Height: req.Height, Hash: req.AppHash, diff --git a/finality-provider/store/storedfp.go b/finality-provider/store/storedfp.go index 84c2c4b0..70fe9f43 100644 --- a/finality-provider/store/storedfp.go +++ b/finality-provider/store/storedfp.go @@ -81,10 +81,12 @@ func (sfp *StoredFinalityProvider) ToFinalityProviderInfo() *proto.FinalityProvi // ShouldStart returns true if the finality provider should start his instance // based on the current status of the finality provider. // -// It returns false if the status is either 'CREATED' or 'SLASHED'. +// It returns false if the status is either 'CREATED', 'JAILED' or 'SLASHED'. // It returs true for all the other status. func (sfp *StoredFinalityProvider) ShouldStart() bool { - if sfp.Status == proto.FinalityProviderStatus_CREATED || sfp.Status == proto.FinalityProviderStatus_SLASHED { + if sfp.Status == proto.FinalityProviderStatus_CREATED || + sfp.Status == proto.FinalityProviderStatus_SLASHED || + sfp.Status == proto.FinalityProviderStatus_JAILED { return false } diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 32432ad1..6d87e81f 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -26,11 +26,9 @@ var ( // activation with BTC delegation and Covenant sig -> // vote submission -> block finalization func TestFinalityProviderLifeCycle(t *testing.T) { - tm, fpInsList := StartManagerWithFinalityProvider(t, 1) + tm, fpIns := StartManagerWithFinalityProvider(t) defer tm.Stop(t) - fpIns := fpInsList[0] - // check the public randomness is committed tm.WaitForFpPubRandTimestamped(t, fpIns) @@ -58,11 +56,9 @@ func TestFinalityProviderLifeCycle(t *testing.T) { // sends a finality vote over a conflicting block // in this case, the BTC private key should be extracted by Babylon func TestDoubleSigning(t *testing.T) { - tm, fpInsList := StartManagerWithFinalityProvider(t, 1) + tm, fpIns := StartManagerWithFinalityProvider(t) defer tm.Stop(t) - fpIns := fpInsList[0] - // check the public randomness is committed tm.WaitForFpPubRandTimestamped(t, fpIns) @@ -102,10 +98,10 @@ func TestDoubleSigning(t *testing.T) { t.Logf("the equivocation attack is successful") - tm.WaitForFpShutDown(t, fpIns.GetBtcPkBIP340()) + tm.WaitForFpShutDown(t) // try to start all the finality providers and the slashed one should not be restarted - err = tm.Fpa.StartHandlingAll() + err = tm.Fpa.StartHandlingFinalityProvider(fpIns.GetBtcPkBIP340(), "") require.NoError(t, err) fps, err := tm.Fpa.ListAllFinalityProvidersInfo() require.NoError(t, err) @@ -116,11 +112,9 @@ func TestDoubleSigning(t *testing.T) { // TestFastSync tests the fast sync process where the finality-provider is terminated and restarted with fast sync func TestFastSync(t *testing.T) { - tm, fpInsList := StartManagerWithFinalityProvider(t, 1) + tm, fpIns := StartManagerWithFinalityProvider(t) defer tm.Stop(t) - fpIns := fpInsList[0] - // check the public randomness is committed tm.WaitForFpPubRandTimestamped(t, fpIns) diff --git a/itest/test_manager.go b/itest/test_manager.go index a18397ba..96d75984 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -6,7 +6,6 @@ import ( "math/rand" "os" "path/filepath" - "strconv" "strings" "sync" "testing" @@ -50,12 +49,12 @@ var ( eventuallyPollTime = 500 * time.Millisecond btcNetworkParams = &chaincfg.SimNetParams - fpNamePrefix = "test-fp-" - monikerPrefix = "moniker-" - chainID = "chain-test" - passphrase = "testpass" - hdPath = "" - simnetParams = &chaincfg.SimNetParams + testFpName = "test-fp" + testMoniker = "test-moniker" + testChainID = "chain-test" + passphrase = "testpass" + hdPath = "" + simnetParams = &chaincfg.SimNetParams ) type TestManager struct { @@ -179,83 +178,77 @@ func (tm *TestManager) WaitForServicesStart(t *testing.T) { t.Logf("Babylon node is started") } -func StartManagerWithFinalityProvider(t *testing.T, n int) (*TestManager, []*service.FinalityProviderInstance) { +func StartManagerWithFinalityProvider(t *testing.T) (*TestManager, *service.FinalityProviderInstance) { tm := StartManager(t) app := tm.Fpa cfg := app.GetConfig() oldKey := cfg.BabylonConfig.Key - for i := 0; i < n; i++ { - fpName := fpNamePrefix + strconv.Itoa(i) - moniker := monikerPrefix + strconv.Itoa(i) - commission := sdkmath.LegacyZeroDec() - desc := newDescription(moniker) + commission := sdkmath.LegacyZeroDec() + desc := newDescription(testMoniker) - // needs to update key in config to be able to register and sign the creation of the finality provider with the - // same address. - cfg.BabylonConfig.Key = fpName - fpBbnKeyInfo, err := service.CreateChainKey(cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyringBackend, passphrase, hdPath, "") - require.NoError(t, err) + // needs to update key in config to be able to register and sign the creation of the finality provider with the + // same address. + cfg.BabylonConfig.Key = testFpName + fpBbnKeyInfo, err := service.CreateChainKey(cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyringBackend, passphrase, hdPath, "") + require.NoError(t, err) - cc, err := clientcontroller.NewClientController(cfg.ChainName, cfg.BabylonConfig, &cfg.BTCNetParams, zap.NewNop()) - require.NoError(t, err) - app.UpdateClientController(cc) + cc, err := clientcontroller.NewClientController(cfg.ChainName, cfg.BabylonConfig, &cfg.BTCNetParams, zap.NewNop()) + require.NoError(t, err) + app.UpdateClientController(cc) - // add some funds for new fp pay for fees '-' - _, _, err = tm.manager.BabylondTxBankSend(t, fpBbnKeyInfo.AccAddress.String(), "1000000ubbn", "node0") - require.NoError(t, err) + // add some funds for new fp pay for fees '-' + _, _, err = tm.manager.BabylondTxBankSend(t, fpBbnKeyInfo.AccAddress.String(), "1000000ubbn", "node0") + require.NoError(t, err) - res, err := app.CreateFinalityProvider(fpName, chainID, passphrase, hdPath, nil, desc, &commission) - require.NoError(t, err) - fpPk, err := bbntypes.NewBIP340PubKeyFromHex(res.FpInfo.BtcPkHex) - require.NoError(t, err) + res, err := app.CreateFinalityProvider(testFpName, testChainID, passphrase, hdPath, nil, desc, &commission) + require.NoError(t, err) + fpPk, err := bbntypes.NewBIP340PubKeyFromHex(res.FpInfo.BtcPkHex) + require.NoError(t, err) - _, err = app.RegisterFinalityProvider(fpPk.MarshalHex()) - require.NoError(t, err) - err = app.StartHandlingFinalityProvider(fpPk, passphrase) - require.NoError(t, err) - fpIns, err := app.GetFinalityProviderInstance(fpPk) - require.NoError(t, err) - require.True(t, fpIns.IsRunning()) - require.NoError(t, err) + _, err = app.RegisterFinalityProvider(fpPk.MarshalHex()) + require.NoError(t, err) + err = app.StartHandlingFinalityProvider(fpPk, passphrase) + require.NoError(t, err) + fpIns, err := app.GetFinalityProviderInstance() + require.NoError(t, err) + require.True(t, fpIns.IsRunning()) + require.NoError(t, err) - // check finality providers on Babylon side - require.Eventually(t, func() bool { - fps, err := tm.BBNClient.QueryFinalityProviders() - if err != nil { - t.Logf("failed to query finality providers from Babylon %s", err.Error()) - return false - } + // check finality providers on Babylon side + var fpRes *bstypes.FinalityProviderResponse + require.Eventually(t, func() bool { + fps, err := tm.BBNClient.QueryFinalityProviders() + if err != nil { + t.Logf("failed to query finality providers from Babylon %s", err.Error()) + return false + } - if len(fps) != i+1 { - return false - } + if len(fps) != 1 { + return false + } - for _, fp := range fps { - if !strings.Contains(fp.Description.Moniker, monikerPrefix) { - return false - } - if !fp.Commission.Equal(sdkmath.LegacyZeroDec()) { - return false - } - } + fpRes = fps[0] - return true - }, eventuallyWaitTimeOut, eventuallyPollTime) - } + if !strings.Contains(fpRes.Description.Moniker, testMoniker) { + return false + } + if !fpRes.Commission.Equal(sdkmath.LegacyZeroDec()) { + return false + } + + return true + }, eventuallyWaitTimeOut, eventuallyPollTime) // goes back to old key in app cfg.BabylonConfig.Key = oldKey - cc, err := clientcontroller.NewClientController(cfg.ChainName, cfg.BabylonConfig, &cfg.BTCNetParams, zap.NewNop()) + cc, err = clientcontroller.NewClientController(cfg.ChainName, cfg.BabylonConfig, &cfg.BTCNetParams, zap.NewNop()) require.NoError(t, err) app.UpdateClientController(cc) - fpInsList := app.ListFinalityProviderInstances() - require.Equal(t, n, len(fpInsList)) - - t.Logf("the test manager is running with %v finality-provider(s)", len(fpInsList)) + t.Logf("the test manager is running with a finality provider") - return tm, fpInsList + return tm, fpIns } func (tm *TestManager) Stop(t *testing.T) { @@ -407,13 +400,13 @@ func (tm *TestManager) WaitForNFinalizedBlocks(t *testing.T, n int) []*types.Blo return blocks } -func (tm *TestManager) WaitForFpShutDown(t *testing.T, pk *bbntypes.BIP340PubKey) { +func (tm *TestManager) WaitForFpShutDown(t *testing.T) { require.Eventually(t, func() bool { - _, err := tm.Fpa.GetFinalityProviderInstance(pk) + _, err := tm.Fpa.GetFinalityProviderInstance() return err != nil }, eventuallyWaitTimeOut, eventuallyPollTime) - t.Logf("the finality-provider instance %s is shutdown", pk.MarshalHex()) + t.Logf("the finality-provider instance is shutdown") } func (tm *TestManager) StopAndRestartFpAfterNBlocks(t *testing.T, n int, fpIns *service.FinalityProviderInstance) {