From 56ffa3beb00a906293bd6b7126a09efbc9a832b9 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 18 Sep 2024 10:11:43 -0300 Subject: [PATCH 01/11] chore: add table test to UpdateFpStatusFromVotingPower --- finality-provider/store/fpstore_test.go | 124 ++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index 54feccf7..62d842e9 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/babylonlabs-io/finality-provider/finality-provider/config" + "github.com/babylonlabs-io/finality-provider/finality-provider/proto" fpstore "github.com/babylonlabs-io/finality-provider/finality-provider/store" "github.com/babylonlabs-io/finality-provider/testutil" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,3 +79,126 @@ func FuzzFinalityProvidersStore(f *testing.F) { require.ErrorIs(t, err, fpstore.ErrFinalityProviderNotFound) }) } + +func TestUpdateFpStatusFromVotingPower(t *testing.T) { + r := rand.New(rand.NewSource(10)) + + tcs := []struct { + name string + fpStoredStatus proto.FinalityProviderStatus + votingPowerOnChain uint64 + expStatus proto.FinalityProviderStatus + expErr error + }{ + { + "zero vp: Created to Registered", + proto.FinalityProviderStatus_CREATED, + 0, + proto.FinalityProviderStatus_REGISTERED, + nil, + }, + { + "zero vp: Active to Inactive", + proto.FinalityProviderStatus_ACTIVE, + 0, + proto.FinalityProviderStatus_INACTIVE, + nil, + }, + { + "zero vp: Slashed to Slashed", + proto.FinalityProviderStatus_SLASHED, + 0, + proto.FinalityProviderStatus_SLASHED, + nil, + }, + { + "vp > 0: Slashed to Active", + proto.FinalityProviderStatus_SLASHED, + 15, + proto.FinalityProviderStatus_ACTIVE, + nil, + }, + { + "vp > 0: Created to Active", + proto.FinalityProviderStatus_CREATED, + 1, + proto.FinalityProviderStatus_ACTIVE, + nil, + }, + { + "vp > 0: Inactive to Active", + proto.FinalityProviderStatus_INACTIVE, + 1, + proto.FinalityProviderStatus_ACTIVE, + nil, + }, + { + "err: fp not found and vp > 0", + proto.FinalityProviderStatus_INACTIVE, + 1, + proto.FinalityProviderStatus_INACTIVE, + fpstore.ErrFinalityProviderNotFound, + }, + { + "err: fp not found and vp == 0 && created", + proto.FinalityProviderStatus_CREATED, + 0, + proto.FinalityProviderStatus_SLASHED, + fpstore.ErrFinalityProviderNotFound, + }, + { + "err: fp not found and vp == 0 && active", + proto.FinalityProviderStatus_ACTIVE, + 0, + proto.FinalityProviderStatus_SLASHED, + fpstore.ErrFinalityProviderNotFound, + }, + } + + homePath := t.TempDir() + cfg := config.DefaultDBConfigWithHomePath(homePath) + + fpdb, err := cfg.GetDbBackend() + require.NoError(t, err) + fps, err := fpstore.NewFinalityProviderStore(fpdb) + require.NoError(t, err) + + defer func() { + err := fpdb.Close() + require.NoError(t, err) + err = os.RemoveAll(homePath) + require.NoError(t, err) + }() + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + fp := testutil.GenRandomFinalityProvider(r, t) + fp.Status = tc.fpStoredStatus + if tc.expErr == nil { + err = fps.CreateFinalityProvider( + sdk.MustAccAddressFromBech32(fp.FPAddr), + fp.BtcPk, + fp.Description, + fp.Commission, + fp.KeyName, + fp.ChainID, + fp.Pop.BtcSig, + ) + require.NoError(t, err) + fps.SetFpStatus(fp.BtcPk, fp.Status) + } + + actStatus, err := fps.UpdateFpStatusFromVotingPower(tc.votingPowerOnChain, fp) + if tc.expErr != nil { + require.EqualError(t, err, tc.expErr.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tc.expStatus, actStatus) + + storedFp, err := fps.GetFinalityProvider(fp.BtcPk) + require.NoError(t, err) + require.Equal(t, tc.expStatus, storedFp.Status) + }) + } +} From 95d20c726064681b83d855667e0330653897acaf Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 18 Sep 2024 10:37:28 -0300 Subject: [PATCH 02/11] chore: add table testing for ShouldSyncStatusFromVotingPower and ShouldStart --- finality-provider/store/storedfp_test.go | 124 +++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 finality-provider/store/storedfp_test.go diff --git a/finality-provider/store/storedfp_test.go b/finality-provider/store/storedfp_test.go new file mode 100644 index 00000000..29278465 --- /dev/null +++ b/finality-provider/store/storedfp_test.go @@ -0,0 +1,124 @@ +package store_test + +import ( + "math/rand" + "testing" + + "github.com/babylonlabs-io/finality-provider/finality-provider/proto" + "github.com/babylonlabs-io/finality-provider/testutil" + "github.com/stretchr/testify/require" +) + +func TestShouldStart(t *testing.T) { + tcs := []struct { + name string + currFpStatus proto.FinalityProviderStatus + expShouldStart bool + }{ + { + "Created: Should NOT start", + proto.FinalityProviderStatus_CREATED, + false, + }, + { + "Slashed: Should NOT start", + proto.FinalityProviderStatus_SLASHED, + false, + }, + { + "Inactive: Should start", + proto.FinalityProviderStatus_INACTIVE, + true, + }, + { + "Registered: Should start", + proto.FinalityProviderStatus_REGISTERED, + true, + }, + { + "Active: Should start", + proto.FinalityProviderStatus_ACTIVE, + true, + }, + } + + r := rand.New(rand.NewSource(10)) + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + fp := testutil.GenRandomFinalityProvider(r, t) + fp.Status = tc.currFpStatus + + shouldStart := fp.ShouldStart() + require.Equal(t, tc.expShouldStart, shouldStart) + }) + } +} + +func TestShouldSyncStatusFromVotingPower(t *testing.T) { + tcs := []struct { + name string + votingPowerOnChain uint64 + currFpStatus proto.FinalityProviderStatus + expShouldSyncStatus bool + }{ + { + "zero vp and Registered: Should NOT sync", + 0, + proto.FinalityProviderStatus_REGISTERED, + false, + }, + { + "zero vp and Inactive: Should NOT sync", + 0, + proto.FinalityProviderStatus_INACTIVE, + false, + }, + { + "zero vp and Slashed: Should NOT sync", + 0, + proto.FinalityProviderStatus_SLASHED, + false, + }, + { + "zero vp and Created: Should sync", + 0, + proto.FinalityProviderStatus_CREATED, + true, + }, + { + "zero vp and Active: Should sync", + 0, + proto.FinalityProviderStatus_ACTIVE, + true, + }, + { + "some vp: Should sync", + 1, + proto.FinalityProviderStatus_SLASHED, + true, + }, + { + "some vp: Should sync from even inactive", + 1, + proto.FinalityProviderStatus_INACTIVE, + true, + }, + { + "some vp: Should sync even if it is already active", + 10, + proto.FinalityProviderStatus_ACTIVE, + true, + }, + } + + r := rand.New(rand.NewSource(10)) + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + fp := testutil.GenRandomFinalityProvider(r, t) + fp.Status = tc.currFpStatus + + shouldSync := fp.ShouldSyncStatusFromVotingPower(tc.votingPowerOnChain) + require.Equal(t, tc.expShouldSyncStatus, shouldSync) + }) + } +} From 93eacf4cb2732d6724c8fd738bfd5f12544b0be2 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 18 Sep 2024 12:28:39 -0300 Subject: [PATCH 03/11] chore: add test to check syncChainFpStatusLoop actually modified the fp status to Active if it has VP on chain --- finality-provider/service/app_test.go | 60 +++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index 49bdeb46..f5aa1e93 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -4,7 +4,9 @@ import ( "math/rand" "os" "path/filepath" + "strings" "testing" + "time" bbntypes "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" @@ -138,3 +140,61 @@ func FuzzRegisterFinalityProvider(f *testing.F) { require.Equal(t, true, fpInfo.IsRunning) }) } + +func FuzzSyncFinalityProviderStatus(f *testing.F) { + testutil.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(2)) + + logger := zap.NewNop() + + // create an EOTS manager + eotsHomeDir := filepath.Join(t.TempDir(), "eots-home") + eotsCfg := eotscfg.DefaultConfigWithHomePath(eotsHomeDir) + dbBackend, err := eotsCfg.DatabaseConfig.GetDbBackend() + require.NoError(t, err) + em, err := eotsmanager.NewLocalEOTSManager(eotsHomeDir, eotsCfg.KeyringBackend, dbBackend, logger) + require.NoError(t, err) + defer func() { + dbBackend.Close() + err = os.RemoveAll(eotsHomeDir) + require.NoError(t, err) + }() + + // Create randomized config + fpHomeDir := filepath.Join(t.TempDir(), "fp-home") + fpCfg := config.DefaultConfigWithHome(fpHomeDir) + fpCfg.SyncFpStatusInterval = time.Millisecond * 100 + fpdb, err := fpCfg.DatabaseConfig.GetDbBackend() + require.NoError(t, err) + + randomStartingHeight := uint64(r.Int63n(100) + 1) + currentHeight := randomStartingHeight + uint64(r.Int63n(10)+2) + mockClientController := testutil.PrepareMockedClientController(t, r, randomStartingHeight, currentHeight) + + blkInfo := &types.BlockInfo{Height: currentHeight} + mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return(nil, nil).AnyTimes() + mockClientController.EXPECT().QueryBestBlock().Return(blkInfo, nil).AnyTimes() + mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(2), nil).AnyTimes() + mockClientController.EXPECT().QueryBlock(gomock.Any()).Return(blkInfo, nil).AnyTimes() + + app, err := service.NewFinalityProviderApp(&fpCfg, mockClientController, em, fpdb, logger) + require.NoError(t, err) + + err = app.Start() + require.NoError(t, err) + defer func() { + err = app.Stop() + require.NoError(t, err) + }() + + fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath, nil) + + require.Eventually(t, func() bool { + fpInfo, err := app.GetFinalityProviderInfo(fp.GetBIP340BTCPK()) + require.NoError(t, err) + + return strings.EqualFold(fpInfo.Status, proto.FinalityProviderStatus_ACTIVE.String()) + }, time.Second*5, time.Millisecond*200, "should eventually become active") + }) +} From 093af6ba8c684061fb91644c578ab975ec0d87da Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 18 Sep 2024 13:14:40 -0300 Subject: [PATCH 04/11] fix: lint add err check --- finality-provider/store/fpstore_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index 62d842e9..1b2b3a55 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -185,7 +185,9 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { fp.Pop.BtcSig, ) require.NoError(t, err) - fps.SetFpStatus(fp.BtcPk, fp.Status) + + err = fps.SetFpStatus(fp.BtcPk, fp.Status) + require.NoError(t, err) } actStatus, err := fps.UpdateFpStatusFromVotingPower(tc.votingPowerOnChain, fp) From d8fa3c6a46d0a6438c558f8a9e12ec6e73a69a08 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 13:30:43 -0300 Subject: [PATCH 05/11] chore: updated to avoid updated status if the fp is slashed --- finality-provider/store/errors.go | 3 +++ finality-provider/store/fpstore.go | 4 ++++ finality-provider/store/fpstore_test.go | 17 +++++++++-------- finality-provider/store/storedfp.go | 6 ++++++ finality-provider/store/storedfp_test.go | 10 ++++++++-- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/finality-provider/store/errors.go b/finality-provider/store/errors.go index ad31420d..827da2fb 100644 --- a/finality-provider/store/errors.go +++ b/finality-provider/store/errors.go @@ -17,4 +17,7 @@ var ( // ErrPubRandProofNotFound The finality provider we try update is not found in db ErrPubRandProofNotFound = errors.New("public randomness proof not found") + + // ErrSlashedNotUpdateStatus The finality provider should not update the status slashed to another. + ErrSlashedNotUpdateStatus = errors.New("slash finality provider should not update the status") ) diff --git a/finality-provider/store/fpstore.go b/finality-provider/store/fpstore.go index 58c9f0dc..a9903e68 100644 --- a/finality-provider/store/fpstore.go +++ b/finality-provider/store/fpstore.go @@ -118,6 +118,10 @@ func (s *FinalityProviderStore) UpdateFpStatusFromVotingPower( vp uint64, fp *StoredFinalityProvider, ) (proto.FinalityProviderStatus, error) { + if fp.Status == proto.FinalityProviderStatus_SLASHED { + return proto.FinalityProviderStatus_SLASHED, ErrSlashedNotUpdateStatus + } + if vp > 0 { // voting power > 0 then set the status to ACTIVE return proto.FinalityProviderStatus_ACTIVE, s.SetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_ACTIVE) diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index 1b2b3a55..f3c8ed75 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -82,6 +82,7 @@ func FuzzFinalityProvidersStore(f *testing.F) { func TestUpdateFpStatusFromVotingPower(t *testing.T) { r := rand.New(rand.NewSource(10)) + anyFpStatus := proto.FinalityProviderStatus(100) tcs := []struct { name string @@ -108,15 +109,15 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { "zero vp: Slashed to Slashed", proto.FinalityProviderStatus_SLASHED, 0, - proto.FinalityProviderStatus_SLASHED, - nil, + anyFpStatus, + fpstore.ErrSlashedNotUpdateStatus, }, { - "vp > 0: Slashed to Active", + "err: Slashed should not update status", proto.FinalityProviderStatus_SLASHED, 15, - proto.FinalityProviderStatus_ACTIVE, - nil, + anyFpStatus, + fpstore.ErrSlashedNotUpdateStatus, }, { "vp > 0: Created to Active", @@ -136,21 +137,21 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { "err: fp not found and vp > 0", proto.FinalityProviderStatus_INACTIVE, 1, - proto.FinalityProviderStatus_INACTIVE, + anyFpStatus, fpstore.ErrFinalityProviderNotFound, }, { "err: fp not found and vp == 0 && created", proto.FinalityProviderStatus_CREATED, 0, - proto.FinalityProviderStatus_SLASHED, + anyFpStatus, fpstore.ErrFinalityProviderNotFound, }, { "err: fp not found and vp == 0 && active", proto.FinalityProviderStatus_ACTIVE, 0, - proto.FinalityProviderStatus_SLASHED, + anyFpStatus, fpstore.ErrFinalityProviderNotFound, }, } diff --git a/finality-provider/store/storedfp.go b/finality-provider/store/storedfp.go index 08a86402..380fe714 100644 --- a/finality-provider/store/storedfp.go +++ b/finality-provider/store/storedfp.go @@ -81,9 +81,15 @@ func (sfp *StoredFinalityProvider) ToFinalityProviderInfo() *proto.FinalityProvi // ShouldSyncStatusFromVotingPower returns true if the status should be updated // based on the provided voting power or the current status of the finality provider. // +// It returns false if the status is SLASHED. // It returns true if the voting power is greater than zero, or if the status // is either 'CREATED' or 'ACTIVE'. func (sfp *StoredFinalityProvider) ShouldSyncStatusFromVotingPower(vp uint64) bool { + if sfp.Status == proto.FinalityProviderStatus_SLASHED { + // slashed FP should never sync status + return false + } + if vp > 0 { return true } diff --git a/finality-provider/store/storedfp_test.go b/finality-provider/store/storedfp_test.go index 29278465..7503a450 100644 --- a/finality-provider/store/storedfp_test.go +++ b/finality-provider/store/storedfp_test.go @@ -92,11 +92,17 @@ func TestShouldSyncStatusFromVotingPower(t *testing.T) { true, }, { - "some vp: Should sync", + "some vp and not slashed: Should sync", 1, - proto.FinalityProviderStatus_SLASHED, + proto.FinalityProviderStatus_REGISTERED, true, }, + { + "some vp: Should not sync if it is slashed", + 1, + proto.FinalityProviderStatus_SLASHED, + false, + }, { "some vp: Should sync from even inactive", 1, From 80d30109fdc3e3df46554b1a1ed0ac9b86e8bfbc Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 14:16:13 -0300 Subject: [PATCH 06/11] chore: add test case for voting power table not activated yet --- finality-provider/service/app_test.go | 49 +++++++++++++++++-------- finality-provider/store/fpstore.go | 1 + finality-provider/store/fpstore_test.go | 7 ++++ 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index f5aa1e93..bb6e627e 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -1,6 +1,8 @@ package service_test import ( + "errors" + "fmt" "math/rand" "os" "path/filepath" @@ -144,7 +146,7 @@ func FuzzRegisterFinalityProvider(f *testing.F) { func FuzzSyncFinalityProviderStatus(f *testing.F) { testutil.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(2)) + r := rand.New(rand.NewSource(seed)) logger := zap.NewNop() @@ -155,16 +157,12 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { require.NoError(t, err) em, err := eotsmanager.NewLocalEOTSManager(eotsHomeDir, eotsCfg.KeyringBackend, dbBackend, logger) require.NoError(t, err) - defer func() { - dbBackend.Close() - err = os.RemoveAll(eotsHomeDir) - require.NoError(t, err) - }() // Create randomized config fpHomeDir := filepath.Join(t.TempDir(), "fp-home") fpCfg := config.DefaultConfigWithHome(fpHomeDir) fpCfg.SyncFpStatusInterval = time.Millisecond * 100 + fpCfg.StatusUpdateInterval = time.Minute * 10 // does not test this fpdb, err := fpCfg.DatabaseConfig.GetDbBackend() require.NoError(t, err) @@ -175,26 +173,45 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { blkInfo := &types.BlockInfo{Height: currentHeight} mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return(nil, nil).AnyTimes() mockClientController.EXPECT().QueryBestBlock().Return(blkInfo, nil).AnyTimes() - mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(2), nil).AnyTimes() mockClientController.EXPECT().QueryBlock(gomock.Any()).Return(blkInfo, nil).AnyTimes() + noVotingPowerTable := r.Int31n(10) > 5 + if noVotingPowerTable { + allowedErr := fmt.Sprintf("failed to query Finality Voting Power at Height %d: rpc error: code = Unknown desc = %s: unknown request", currentHeight, bstypes.ErrVotingPowerTableNotUpdated.Wrapf("height: %d", currentHeight).Error()) + mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(0), errors.New(allowedErr)).AnyTimes() + mockClientController.EXPECT().QueryActivatedHeight().Return(uint64(0), errors.New(allowedErr)).AnyTimes() + } else { + mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(2), nil).AnyTimes() + } + app, err := service.NewFinalityProviderApp(&fpCfg, mockClientController, em, fpdb, logger) require.NoError(t, err) err = app.Start() require.NoError(t, err) - defer func() { - err = app.Stop() - require.NoError(t, err) - }() - fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath, nil) + fp := testutil.GenStoredFinalityProvider(r, t, app, "", hdPath, nil) require.Eventually(t, func() bool { - fpInfo, err := app.GetFinalityProviderInfo(fp.GetBIP340BTCPK()) - require.NoError(t, err) - - return strings.EqualFold(fpInfo.Status, proto.FinalityProviderStatus_ACTIVE.String()) + fpPk := fp.GetBIP340BTCPK() + fpInfo, err := app.GetFinalityProviderInfo(fpPk) + if err != nil { + return false + } + + fpInstance, err := app.GetFinalityProviderInstance(fpPk) + if err != nil { + return false + } + + expectedStatus := proto.FinalityProviderStatus_ACTIVE + if noVotingPowerTable { + expectedStatus = proto.FinalityProviderStatus_REGISTERED + } + + btcPkEqual := fpInstance.GetBtcPk().IsEqual(fp.BtcPk) + statusEqual := strings.EqualFold(fpInfo.Status, expectedStatus.String()) + return statusEqual && btcPkEqual }, time.Second*5, time.Millisecond*200, "should eventually become active") }) } diff --git a/finality-provider/store/fpstore.go b/finality-provider/store/fpstore.go index a9903e68..7bb45f54 100644 --- a/finality-provider/store/fpstore.go +++ b/finality-provider/store/fpstore.go @@ -119,6 +119,7 @@ func (s *FinalityProviderStore) UpdateFpStatusFromVotingPower( fp *StoredFinalityProvider, ) (proto.FinalityProviderStatus, error) { if fp.Status == proto.FinalityProviderStatus_SLASHED { + // Slashed FP should not update status return proto.FinalityProviderStatus_SLASHED, ErrSlashedNotUpdateStatus } diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index f3c8ed75..1b4bcce3 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -105,6 +105,13 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { proto.FinalityProviderStatus_INACTIVE, nil, }, + { + "zero vp: Registered should not update the status, but also not error out", + proto.FinalityProviderStatus_REGISTERED, + 0, + proto.FinalityProviderStatus_REGISTERED, + nil, + }, { "zero vp: Slashed to Slashed", proto.FinalityProviderStatus_SLASHED, From 8482684dfe97ae5f9ec2a7b5f8a9316437494c31 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 15:32:04 -0300 Subject: [PATCH 07/11] fixme: failing test, bad mocks? --- finality-provider/service/app.go | 2 +- finality-provider/service/app_test.go | 47 ++++++++++++++++++++------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/finality-provider/service/app.go b/finality-provider/service/app.go index 39ea9d96..96b201a7 100644 --- a/finality-provider/service/app.go +++ b/finality-provider/service/app.go @@ -697,6 +697,7 @@ func (app *FinalityProviderApp) syncChainFpStatusLoop() { zap.Float64("interval seconds", interval.Seconds()), ) syncFpStatusTicker := time.NewTicker(interval) + defer syncFpStatusTicker.Stop() for { select { @@ -710,7 +711,6 @@ func (app *FinalityProviderApp) syncChainFpStatusLoop() { } case <-app.quit: - syncFpStatusTicker.Stop() app.logger.Info("exiting sync FP status loop") return } diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index bb6e627e..d4c32144 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -10,11 +10,13 @@ import ( "testing" "time" + "github.com/babylonlabs-io/babylon/testutil/datagen" bbntypes "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/babylonlabs-io/finality-provider/eotsmanager" eotscfg "github.com/babylonlabs-io/finality-provider/eotsmanager/config" @@ -144,25 +146,35 @@ func FuzzRegisterFinalityProvider(f *testing.F) { } func FuzzSyncFinalityProviderStatus(f *testing.F) { - testutil.AddRandomSeedsToFuzzer(f, 10) + testutil.AddRandomSeedsToFuzzer(f, 14) f.Fuzz(func(t *testing.T, seed int64) { + fmt.Printf("\n\nseed: %d\n\n", seed) r := rand.New(rand.NewSource(seed)) - logger := zap.NewNop() + logger := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()), os.Stderr, zap.DebugLevel), zap.AddStacktrace(zap.DebugLevel)) + pathSuffix := datagen.GenRandomHexStr(r, 10) // create an EOTS manager - eotsHomeDir := filepath.Join(t.TempDir(), "eots-home") + eotsHomeDir := filepath.Join(t.TempDir(), "eots-home", pathSuffix) eotsCfg := eotscfg.DefaultConfigWithHomePath(eotsHomeDir) dbBackend, err := eotsCfg.DatabaseConfig.GetDbBackend() require.NoError(t, err) em, err := eotsmanager.NewLocalEOTSManager(eotsHomeDir, eotsCfg.KeyringBackend, dbBackend, logger) require.NoError(t, err) + defer func() { + err = dbBackend.Close() + require.NoError(t, err) + err = os.RemoveAll(eotsHomeDir) + require.NoError(t, err) + }() // Create randomized config - fpHomeDir := filepath.Join(t.TempDir(), "fp-home") + fpHomeDir := filepath.Join(t.TempDir(), "fp-home", pathSuffix) fpCfg := config.DefaultConfigWithHome(fpHomeDir) fpCfg.SyncFpStatusInterval = time.Millisecond * 100 - fpCfg.StatusUpdateInterval = time.Minute * 10 // does not test this + // no need for other intervals to run + fpCfg.StatusUpdateInterval = time.Minute * 10 + fpCfg.SubmissionRetryInterval = time.Minute * 10 fpdb, err := fpCfg.DatabaseConfig.GetDbBackend() require.NoError(t, err) @@ -171,9 +183,13 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { mockClientController := testutil.PrepareMockedClientController(t, r, randomStartingHeight, currentHeight) blkInfo := &types.BlockInfo{Height: currentHeight} - mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return(nil, nil).AnyTimes() + finalizedBlock := &types.BlockInfo{Height: currentHeight - 1} + mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return([]*types.BlockInfo{finalizedBlock}, nil).AnyTimes() mockClientController.EXPECT().QueryBestBlock().Return(blkInfo, nil).AnyTimes() mockClientController.EXPECT().QueryBlock(gomock.Any()).Return(blkInfo, nil).AnyTimes() + mockClientController.EXPECT().QueryBlocks(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockClientController.EXPECT().QueryActivatedHeight().Return(currentHeight, nil).AnyTimes() + // mockClientController.EXPECT().QueryLastCommittedPublicRand(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() noVotingPowerTable := r.Int31n(10) > 5 if noVotingPowerTable { @@ -190,6 +206,13 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { err = app.Start() require.NoError(t, err) + defer func() { + err = app.Stop() + require.NoError(t, err) + err = os.RemoveAll(fpHomeDir) + require.NoError(t, err) + }() + fp := testutil.GenStoredFinalityProvider(r, t, app, "", hdPath, nil) require.Eventually(t, func() bool { @@ -199,19 +222,19 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { return false } - fpInstance, err := app.GetFinalityProviderInstance(fpPk) - if err != nil { - return false - } - expectedStatus := proto.FinalityProviderStatus_ACTIVE if noVotingPowerTable { expectedStatus = proto.FinalityProviderStatus_REGISTERED } + fpInstance, err := app.GetFinalityProviderInstance(fpPk) + if err != nil { + return false + } + // TODO: verify why mocks are failing btcPkEqual := fpInstance.GetBtcPk().IsEqual(fp.BtcPk) statusEqual := strings.EqualFold(fpInfo.Status, expectedStatus.String()) return statusEqual && btcPkEqual - }, time.Second*5, time.Millisecond*200, "should eventually become active") + }, time.Second*5, time.Millisecond*200, "should eventually be registered or active") }) } From 7ce88cd697d4344360bb7897dfcaced26dd45c6d Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 17:27:44 -0300 Subject: [PATCH 08/11] fix: test should just return error for getting block to stop pooling --- finality-provider/service/app_test.go | 29 ++++++--------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index d4c32144..c9772897 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -16,7 +16,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "go.uber.org/zap" - "go.uber.org/zap/zapcore" "github.com/babylonlabs-io/finality-provider/eotsmanager" eotscfg "github.com/babylonlabs-io/finality-provider/eotsmanager/config" @@ -148,10 +147,9 @@ func FuzzRegisterFinalityProvider(f *testing.F) { func FuzzSyncFinalityProviderStatus(f *testing.F) { testutil.AddRandomSeedsToFuzzer(f, 14) f.Fuzz(func(t *testing.T, seed int64) { - fmt.Printf("\n\nseed: %d\n\n", seed) r := rand.New(rand.NewSource(seed)) - logger := zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()), os.Stderr, zap.DebugLevel), zap.AddStacktrace(zap.DebugLevel)) + logger := zap.NewNop() pathSuffix := datagen.GenRandomHexStr(r, 10) // create an EOTS manager @@ -161,12 +159,6 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { require.NoError(t, err) em, err := eotsmanager.NewLocalEOTSManager(eotsHomeDir, eotsCfg.KeyringBackend, dbBackend, logger) require.NoError(t, err) - defer func() { - err = dbBackend.Close() - require.NoError(t, err) - err = os.RemoveAll(eotsHomeDir) - require.NoError(t, err) - }() // Create randomized config fpHomeDir := filepath.Join(t.TempDir(), "fp-home", pathSuffix) @@ -183,13 +175,10 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { mockClientController := testutil.PrepareMockedClientController(t, r, randomStartingHeight, currentHeight) blkInfo := &types.BlockInfo{Height: currentHeight} - finalizedBlock := &types.BlockInfo{Height: currentHeight - 1} - mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return([]*types.BlockInfo{finalizedBlock}, nil).AnyTimes() - mockClientController.EXPECT().QueryBestBlock().Return(blkInfo, nil).AnyTimes() - mockClientController.EXPECT().QueryBlock(gomock.Any()).Return(blkInfo, nil).AnyTimes() - mockClientController.EXPECT().QueryBlocks(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - mockClientController.EXPECT().QueryActivatedHeight().Return(currentHeight, nil).AnyTimes() - // mockClientController.EXPECT().QueryLastCommittedPublicRand(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + + mockClientController.EXPECT().QueryLatestFinalizedBlocks(gomock.Any()).Return(nil, nil).AnyTimes() + mockClientController.EXPECT().QueryBestBlock().Return(blkInfo, nil).Return(blkInfo, nil).AnyTimes() + mockClientController.EXPECT().QueryBlock(gomock.Any()).Return(nil, errors.New("chain not online")).AnyTimes() noVotingPowerTable := r.Int31n(10) > 5 if noVotingPowerTable { @@ -197,6 +186,7 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(0), errors.New(allowedErr)).AnyTimes() mockClientController.EXPECT().QueryActivatedHeight().Return(uint64(0), errors.New(allowedErr)).AnyTimes() } else { + mockClientController.EXPECT().QueryActivatedHeight().Return(currentHeight, nil).AnyTimes() mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), gomock.Any()).Return(uint64(2), nil).AnyTimes() } @@ -206,13 +196,6 @@ func FuzzSyncFinalityProviderStatus(f *testing.F) { err = app.Start() require.NoError(t, err) - defer func() { - err = app.Stop() - require.NoError(t, err) - err = os.RemoveAll(fpHomeDir) - require.NoError(t, err) - }() - fp := testutil.GenStoredFinalityProvider(r, t, app, "", hdPath, nil) require.Eventually(t, func() bool { From f19e77ce3c8c249e38162eb7abd3bdf78394da84 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 21:02:02 -0300 Subject: [PATCH 09/11] chore: removed func ShouldSyncStatusFromVotingPower --- finality-provider/store/errors.go | 3 - finality-provider/store/fpstore.go | 4 +- finality-provider/store/fpstore_test.go | 8 +-- finality-provider/store/storedfp.go | 20 ------- finality-provider/store/storedfp_test.go | 75 ------------------------ 5 files changed, 6 insertions(+), 104 deletions(-) diff --git a/finality-provider/store/errors.go b/finality-provider/store/errors.go index 827da2fb..ad31420d 100644 --- a/finality-provider/store/errors.go +++ b/finality-provider/store/errors.go @@ -17,7 +17,4 @@ var ( // ErrPubRandProofNotFound The finality provider we try update is not found in db ErrPubRandProofNotFound = errors.New("public randomness proof not found") - - // ErrSlashedNotUpdateStatus The finality provider should not update the status slashed to another. - ErrSlashedNotUpdateStatus = errors.New("slash finality provider should not update the status") ) diff --git a/finality-provider/store/fpstore.go b/finality-provider/store/fpstore.go index 7bb45f54..24715279 100644 --- a/finality-provider/store/fpstore.go +++ b/finality-provider/store/fpstore.go @@ -117,10 +117,10 @@ func (s *FinalityProviderStore) SetFpStatus(btcPk *btcec.PublicKey, status proto func (s *FinalityProviderStore) UpdateFpStatusFromVotingPower( vp uint64, fp *StoredFinalityProvider, -) (proto.FinalityProviderStatus, error) { +) (newStatus proto.FinalityProviderStatus, err error) { if fp.Status == proto.FinalityProviderStatus_SLASHED { // Slashed FP should not update status - return proto.FinalityProviderStatus_SLASHED, ErrSlashedNotUpdateStatus + return proto.FinalityProviderStatus_SLASHED, nil } if vp > 0 { diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index 1b4bcce3..1985d453 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -116,15 +116,15 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { "zero vp: Slashed to Slashed", proto.FinalityProviderStatus_SLASHED, 0, - anyFpStatus, - fpstore.ErrSlashedNotUpdateStatus, + proto.FinalityProviderStatus_SLASHED, + nil, }, { "err: Slashed should not update status", proto.FinalityProviderStatus_SLASHED, 15, - anyFpStatus, - fpstore.ErrSlashedNotUpdateStatus, + proto.FinalityProviderStatus_SLASHED, + nil, }, { "vp > 0: Created to Active", diff --git a/finality-provider/store/storedfp.go b/finality-provider/store/storedfp.go index 380fe714..84c2c4b0 100644 --- a/finality-provider/store/storedfp.go +++ b/finality-provider/store/storedfp.go @@ -78,26 +78,6 @@ func (sfp *StoredFinalityProvider) ToFinalityProviderInfo() *proto.FinalityProvi } } -// ShouldSyncStatusFromVotingPower returns true if the status should be updated -// based on the provided voting power or the current status of the finality provider. -// -// It returns false if the status is SLASHED. -// It returns true if the voting power is greater than zero, or if the status -// is either 'CREATED' or 'ACTIVE'. -func (sfp *StoredFinalityProvider) ShouldSyncStatusFromVotingPower(vp uint64) bool { - if sfp.Status == proto.FinalityProviderStatus_SLASHED { - // slashed FP should never sync status - return false - } - - if vp > 0 { - return true - } - - return sfp.Status == proto.FinalityProviderStatus_CREATED || - sfp.Status == proto.FinalityProviderStatus_ACTIVE -} - // ShouldStart returns true if the finality provider should start his instance // based on the current status of the finality provider. // diff --git a/finality-provider/store/storedfp_test.go b/finality-provider/store/storedfp_test.go index 7503a450..13d9f44d 100644 --- a/finality-provider/store/storedfp_test.go +++ b/finality-provider/store/storedfp_test.go @@ -53,78 +53,3 @@ func TestShouldStart(t *testing.T) { }) } } - -func TestShouldSyncStatusFromVotingPower(t *testing.T) { - tcs := []struct { - name string - votingPowerOnChain uint64 - currFpStatus proto.FinalityProviderStatus - expShouldSyncStatus bool - }{ - { - "zero vp and Registered: Should NOT sync", - 0, - proto.FinalityProviderStatus_REGISTERED, - false, - }, - { - "zero vp and Inactive: Should NOT sync", - 0, - proto.FinalityProviderStatus_INACTIVE, - false, - }, - { - "zero vp and Slashed: Should NOT sync", - 0, - proto.FinalityProviderStatus_SLASHED, - false, - }, - { - "zero vp and Created: Should sync", - 0, - proto.FinalityProviderStatus_CREATED, - true, - }, - { - "zero vp and Active: Should sync", - 0, - proto.FinalityProviderStatus_ACTIVE, - true, - }, - { - "some vp and not slashed: Should sync", - 1, - proto.FinalityProviderStatus_REGISTERED, - true, - }, - { - "some vp: Should not sync if it is slashed", - 1, - proto.FinalityProviderStatus_SLASHED, - false, - }, - { - "some vp: Should sync from even inactive", - 1, - proto.FinalityProviderStatus_INACTIVE, - true, - }, - { - "some vp: Should sync even if it is already active", - 10, - proto.FinalityProviderStatus_ACTIVE, - true, - }, - } - - r := rand.New(rand.NewSource(10)) - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - fp := testutil.GenRandomFinalityProvider(r, t) - fp.Status = tc.currFpStatus - - shouldSync := fp.ShouldSyncStatusFromVotingPower(tc.votingPowerOnChain) - require.Equal(t, tc.expShouldSyncStatus, shouldSync) - }) - } -} From 9e8196dc673663eb0e3cb333349067b64e2eb93e Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 21:02:10 -0300 Subject: [PATCH 10/11] chore: removed func ShouldSyncStatusFromVotingPower --- finality-provider/service/app.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/finality-provider/service/app.go b/finality-provider/service/app.go index 96b201a7..6c5c7c2a 100644 --- a/finality-provider/service/app.go +++ b/finality-provider/service/app.go @@ -265,10 +265,6 @@ func (app *FinalityProviderApp) SyncFinalityProviderStatus() (fpInstanceRunning } } - if !fp.ShouldSyncStatusFromVotingPower(vp) { - continue - } - bip340PubKey := fp.GetBIP340BTCPK() if app.fpManager.IsFinalityProviderRunning(bip340PubKey) { // there is a instance running, no need to keep syncing @@ -283,13 +279,15 @@ func (app *FinalityProviderApp) SyncFinalityProviderStatus() (fpInstanceRunning return false, err } - app.logger.Info( - "Update FP status", - zap.String("fp_addr", fp.FPAddr), - zap.String("old_status", oldStatus.String()), - zap.String("new_status", newStatus.String()), - ) - fp.Status = newStatus + if oldStatus != newStatus { + app.logger.Info( + "Update FP status", + zap.String("fp_addr", fp.FPAddr), + zap.String("old_status", oldStatus.String()), + zap.String("new_status", newStatus.String()), + ) + fp.Status = newStatus + } if !fp.ShouldStart() { continue From 33b76d90442e6c9d69d9fea865b955e302050f2d Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 19 Sep 2024 21:06:02 -0300 Subject: [PATCH 11/11] chore: add test case for Registered to Active when it has voting power --- finality-provider/store/fpstore_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/finality-provider/store/fpstore_test.go b/finality-provider/store/fpstore_test.go index 1985d453..eb18a23c 100644 --- a/finality-provider/store/fpstore_test.go +++ b/finality-provider/store/fpstore_test.go @@ -133,6 +133,13 @@ func TestUpdateFpStatusFromVotingPower(t *testing.T) { proto.FinalityProviderStatus_ACTIVE, nil, }, + { + "vp > 0: Registered to Active", + proto.FinalityProviderStatus_REGISTERED, + 1, + proto.FinalityProviderStatus_ACTIVE, + nil, + }, { "vp > 0: Inactive to Active", proto.FinalityProviderStatus_INACTIVE,