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

chore: Remove sync fp status loop #208

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Unreleased

### Improvements

* [#208](https://github.com/babylonlabs-io/finality-provider/pull/208) Remove sync fp status loop

## v0.13.1

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion clientcontroller/babylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (bc *BabylonController) QueryFinalityProviderVotingPower(fpPk *btcec.Public
return 0, nil
}

return 0, fmt.Errorf("failed to query Finality Voting Power at Height %d: %w", blockHeight, err)
return 0, err
}

return res.VotingPower, nil
Expand Down
3 changes: 0 additions & 3 deletions finality-provider/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const (
defaultStatusUpdateInterval = 20 * time.Second
defaultRandomInterval = 30 * time.Second
defaultSubmitRetryInterval = 1 * time.Second
defaultSyncFpStatusInterval = 30 * time.Second
defaultSignatureSubmissionInterval = 1 * time.Second
defaultMaxSubmissionRetries = 20
defaultBitcoinNetwork = "signet"
Expand Down Expand Up @@ -65,7 +64,6 @@ type Config struct {
StatusUpdateInterval time.Duration `long:"statusupdateinterval" description:"The interval between each update of finality-provider status"`
RandomnessCommitInterval time.Duration `long:"randomnesscommitinterval" description:"The interval between each attempt to commit public randomness"`
SubmissionRetryInterval time.Duration `long:"submissionretryinterval" description:"The interval between each attempt to submit finality signature or public randomness after a failure"`
SyncFpStatusInterval time.Duration `long:"syncfpstatusinterval" description:"The duration of time that it should sync FP status with the client blockchain"`
SignatureSubmissionInterval time.Duration `long:"signaturesubmissioninterval" description:"The interval between each finality signature(s) submission"`

BitcoinNetwork string `long:"bitcoinnetwork" description:"Bitcoin network to run on" choise:"mainnet" choice:"regtest" choice:"testnet" choice:"simnet" choice:"signet"`
Expand Down Expand Up @@ -108,7 +106,6 @@ func DefaultConfigWithHome(homePath string) Config {
EOTSManagerAddress: defaultEOTSManagerAddress,
RPCListener: DefaultRPCListener,
Metrics: metrics.DefaultFpConfig(),
SyncFpStatusInterval: defaultSyncFpStatusInterval,
}

if err := cfg.Validate(); err != nil {
Expand Down
91 changes: 59 additions & 32 deletions finality-provider/service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,60 +191,83 @@ func (app *FinalityProviderApp) StartFinalityProvider(fpPk *bbntypes.BIP340PubKe
return nil
}

// SyncFinalityProviderStatus syncs the status of the finality-providers with the chain.
func (app *FinalityProviderApp) SyncFinalityProviderStatus() (bool, error) {
var fpInstanceRunning bool
latestBlock, err := app.cc.QueryBestBlock()
if err != nil {
return false, err
}

// SyncAllFinalityProvidersStatus syncs the status of all the stored finality providers with the chain.
// it should be called before a fp instance is started
func (app *FinalityProviderApp) SyncAllFinalityProvidersStatus() error {
fps, err := app.fps.GetAllStoredFinalityProviders()
if err != nil {
return false, err
return err
}

for _, fp := range fps {
vp, err := app.cc.QueryFinalityProviderVotingPower(fp.BtcPk, latestBlock.Height)
latestBlock, err := app.cc.QueryBestBlock()
if err != nil {
continue
return err
}

bip340PubKey := fp.GetBIP340BTCPK()
if app.IsFinalityProviderRunning(bip340PubKey) {
// there is a instance running, no need to keep syncing
fpInstanceRunning = true
// if it is already running, no need to update status
continue
pkHex := fp.GetBIP340BTCPK().MarshalHex()
power, err := app.cc.QueryFinalityProviderVotingPower(fp.BtcPk, latestBlock.Height)
if err != nil {
return fmt.Errorf("failed to query voting power for finality provider %s at height %d: %w",
fp.GetBIP340BTCPK().MarshalHex(), latestBlock.Height, err)
}

// power > 0 (slashed_height must > 0), set status to ACTIVE
oldStatus := fp.Status
newStatus, err := app.fps.UpdateFpStatusFromVotingPower(vp, fp)
if power > 0 {
if oldStatus != proto.FinalityProviderStatus_ACTIVE {
fp.Status = proto.FinalityProviderStatus_ACTIVE
app.fps.MustSetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_ACTIVE)
app.logger.Debug(
"the finality-provider status is changed to ACTIVE",
zap.String("fp_btc_pk", pkHex),
zap.String("old_status", oldStatus.String()),
zap.Uint64("power", power),
)
}
continue
}
slashed, jailed, err := app.cc.QueryFinalityProviderSlashedOrJailed(fp.BtcPk)
if err != nil {
return false, err
return err
}
if slashed {
app.fps.MustSetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_SLASHED)

if oldStatus != newStatus {
app.logger.Info(
"Update FP status",
zap.String("fp_addr", fp.FPAddr),
app.logger.Debug(
"the finality-provider status is changed to SLAHED",
zap.String("fp_btc_pk", pkHex),
zap.String("old_status", oldStatus.String()),
zap.String("new_status", newStatus.String()),
)
fp.Status = newStatus

continue
}
if jailed {
app.fps.MustSetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_JAILED)

app.logger.Debug(
"the finality-provider status is changed to JAILED",
zap.String("fp_btc_pk", pkHex),
zap.String("old_status", oldStatus.String()),
)

if !fp.ShouldStart() {
continue
}
// power == 0 and slashed_height == 0, change to INACTIVE if the current status is ACTIVE
if oldStatus == proto.FinalityProviderStatus_ACTIVE {
app.fps.MustSetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_INACTIVE)

app.logger.Debug(
"the finality-provider status is changed to INACTIVE",
zap.String("fp_btc_pk", pkHex),
zap.String("old_status", oldStatus.String()),
)

if err := app.StartFinalityProvider(bip340PubKey, ""); err != nil {
return false, err
continue
}
fpInstanceRunning = true
}

return fpInstanceRunning, nil
return nil
}

// Start starts only the finality-provider daemon without any finality-provider instances
Expand All @@ -253,8 +276,12 @@ func (app *FinalityProviderApp) Start() error {
app.startOnce.Do(func() {
app.logger.Info("Starting FinalityProviderApp")

app.wg.Add(6)
go app.syncChainFpStatusLoop()
startErr = app.SyncAllFinalityProvidersStatus()
if startErr != nil {
return
}

app.wg.Add(5)
go app.metricsUpdateLoop()
go app.monitorCriticalErr()
go app.monitorStatusUpdate()
Expand Down
85 changes: 35 additions & 50 deletions finality-provider/service/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func FuzzCreateFinalityProvider(f *testing.F) {
}

func FuzzSyncFinalityProviderStatus(f *testing.F) {
testutil.AddRandomSeedsToFuzzer(f, 14)
testutil.AddRandomSeedsToFuzzer(f, 10)
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))

Expand All @@ -149,12 +149,24 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) {
mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(2), nil).AnyTimes()
}
mockClientController.EXPECT().QueryFinalityProviderHighestVotedHeight(gomock.Any()).Return(uint64(0), nil).AnyTimes()
var isSlashedOrJailed int
if noVotingPowerTable {
// 0 means is slashed, 1 means is jailed, 2 means neither slashed nor jailed
isSlashedOrJailed = r.Intn(3)
switch isSlashedOrJailed {
case 0:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(true, false, nil).AnyTimes()
case 1:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, true, nil).AnyTimes()
case 2:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, false, nil).AnyTimes()
}
}

// Create randomized config
pathSuffix := datagen.GenRandomHexStr(r, 10)
fpHomeDir := filepath.Join(t.TempDir(), "fp-home", pathSuffix)
fpCfg := config.DefaultConfigWithHome(fpHomeDir)
fpCfg.SyncFpStatusInterval = time.Millisecond * 100
// no need for other intervals to run
fpCfg.StatusUpdateInterval = time.Minute * 10
fpCfg.SubmissionRetryInterval = time.Minute * 10
Expand All @@ -163,26 +175,22 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) {
app, fpPk, cleanup := startFPAppWithRegisteredFp(t, r, fpHomeDir, &fpCfg, mockClientController)
defer cleanup()

require.Eventually(t, func() bool {
fpInfo, err := app.GetFinalityProviderInfo(fpPk)
if err != nil {
return false
}
fpInfo, err := app.GetFinalityProviderInfo(fpPk)
require.NoError(t, err)

expectedStatus := proto.FinalityProviderStatus_ACTIVE
if noVotingPowerTable {
expectedStatus := proto.FinalityProviderStatus_ACTIVE
if noVotingPowerTable {
switch isSlashedOrJailed {
case 0:
expectedStatus = proto.FinalityProviderStatus_SLASHED
case 1:
expectedStatus = proto.FinalityProviderStatus_JAILED
case 2:
expectedStatus = proto.FinalityProviderStatus_REGISTERED
}
fpInstance, err := app.GetFinalityProviderInstance()
if err != nil {
return false
}
}

// TODO: verify why mocks are failing
btcPkEqual := fpInstance.GetBtcPk().IsEqual(fpPk.MustToBTCPK())
statusEqual := strings.EqualFold(fpInfo.Status, expectedStatus.String())
return statusEqual && btcPkEqual
}, time.Second*5, time.Millisecond*200, "should eventually be registered or active")
require.Equal(t, fpInfo.Status, expectedStatus.String())
})
}

Expand All @@ -200,7 +208,6 @@ func FuzzUnjailFinalityProvider(f *testing.F) {
fpHomeDir := filepath.Join(t.TempDir(), "fp-home", pathSuffix)
fpCfg := config.DefaultConfigWithHome(fpHomeDir)
// use shorter interval for the test to end faster
fpCfg.SyncFpStatusInterval = time.Millisecond * 10
fpCfg.StatusUpdateInterval = time.Millisecond * 10
fpCfg.SubmissionRetryInterval = time.Millisecond * 10

Expand Down Expand Up @@ -249,51 +256,29 @@ func FuzzStatusUpdate(f *testing.F) {
mockClientController.EXPECT().QueryFinalityProviderHighestVotedHeight(gomock.Any()).Return(uint64(0), nil).AnyTimes()
mockClientController.EXPECT().QueryLastCommittedPublicRand(gomock.Any(), uint64(1)).Return(nil, nil).AnyTimes()
mockClientController.EXPECT().SubmitFinalitySig(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.TxResponse{TxHash: ""}, nil).AnyTimes()
var isSlashedOrJailed int
if votingPower == 0 {
// 0 means is slashed, 1 means is jailed, 2 means neither slashed nor jailed
isSlashedOrJailed = r.Intn(3)
switch isSlashedOrJailed {
case 0:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(true, false, nil).AnyTimes()
case 1:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, true, nil).AnyTimes()
case 2:
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, false, nil).AnyTimes()
}
}
mockClientController.EXPECT().QueryFinalityProviderSlashedOrJailed(gomock.Any()).Return(false, false, nil).AnyTimes()

// Create randomized config
pathSuffix := datagen.GenRandomHexStr(r, 10)
fpHomeDir := filepath.Join(t.TempDir(), "fp-home", pathSuffix)
fpCfg := config.DefaultConfigWithHome(fpHomeDir)
// use shorter interval for the test to end faster
fpCfg.SyncFpStatusInterval = time.Millisecond * 10
fpCfg.StatusUpdateInterval = time.Second * 1
fpCfg.StatusUpdateInterval = time.Millisecond * 10
fpCfg.SubmissionRetryInterval = time.Millisecond * 10

// Create fp app
app, _, cleanup := startFPAppWithRegisteredFp(t, r, fpHomeDir, &fpCfg, mockClientController)
app, fpPk, cleanup := startFPAppWithRegisteredFp(t, r, fpHomeDir, &fpCfg, mockClientController)
defer cleanup()

var fpIns *service.FinalityProviderInstance
var err error
require.Eventually(t, func() bool {
fpIns, err = app.GetFinalityProviderInstance()
return err == nil
}, time.Second*5, time.Millisecond*200, "should eventually be registered or active")
err := app.StartFinalityProvider(fpPk, passphrase)
require.NoError(t, err)
fpIns, err := app.GetFinalityProviderInstance()
require.NoError(t, err)

if votingPower > 0 {
waitForStatus(t, fpIns, proto.FinalityProviderStatus_ACTIVE)
} else {
switch {
case isSlashedOrJailed == 2 && fpIns.GetStatus() == proto.FinalityProviderStatus_ACTIVE:
waitForStatus(t, fpIns, proto.FinalityProviderStatus_INACTIVE)
case isSlashedOrJailed == 1:
waitForStatus(t, fpIns, proto.FinalityProviderStatus_JAILED)
case isSlashedOrJailed == 0:
waitForStatus(t, fpIns, proto.FinalityProviderStatus_SLASHED)
}
} else if fpIns.GetStatus() == proto.FinalityProviderStatus_ACTIVE {
waitForStatus(t, fpIns, proto.FinalityProviderStatus_INACTIVE)
}
})
}
Expand Down
Loading
Loading