diff --git a/CHANGELOG.md b/CHANGELOG.md index 49cc00ef..4497a85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased +* [#124](https://github.com/babylonlabs-io/finality-provider/pull/124) Ignore +duplicated finality vote error + ## v0.10.0 ### Improvements diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index 80f877c0..68524ec8 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -16,8 +16,6 @@ import ( btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types" - fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config" - "github.com/babylonlabs-io/finality-provider/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" @@ -29,6 +27,9 @@ import ( "github.com/cosmos/relayer/v2/relayer/provider" "go.uber.org/zap" protobuf "google.golang.org/protobuf/proto" + + fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config" + "github.com/babylonlabs-io/finality-provider/types" ) var _ ClientController = &BabylonController{} @@ -207,11 +208,19 @@ func (bc *BabylonController) SubmitFinalitySig( btcstakingtypes.ErrFpAlreadySlashed, } - res, err := bc.reliablySendMsg(msg, emptyErrs, unrecoverableErrs) + expectedErrs := []*sdkErr.Error{ + finalitytypes.ErrDuplicatedFinalitySig, + } + + res, err := bc.reliablySendMsg(msg, expectedErrs, unrecoverableErrs) if err != nil { return nil, err } + if res == nil { + return &types.TxResponse{}, nil + } + return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil } @@ -252,11 +261,19 @@ func (bc *BabylonController) SubmitBatchFinalitySigs( btcstakingtypes.ErrFpAlreadySlashed, } - res, err := bc.reliablySendMsgs(msgs, emptyErrs, unrecoverableErrs) + expectedErrs := []*sdkErr.Error{ + finalitytypes.ErrDuplicatedFinalitySig, + } + + res, err := bc.reliablySendMsgs(msgs, expectedErrs, unrecoverableErrs) if err != nil { return nil, err } + if res == nil { + return &types.TxResponse{}, nil + } + return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil } diff --git a/finality-provider/service/fp_instance.go b/finality-provider/service/fp_instance.go index 30b80451..007e4419 100644 --- a/finality-provider/service/fp_instance.go +++ b/finality-provider/service/fp_instance.go @@ -11,12 +11,13 @@ import ( "github.com/avast/retry-go/v4" bbntypes "github.com/babylonlabs-io/babylon/types" ftypes "github.com/babylonlabs-io/babylon/x/finality/types" - fppath "github.com/babylonlabs-io/finality-provider/lib/math" "github.com/btcsuite/btcd/btcec/v2" "github.com/gogo/protobuf/jsonpb" "go.uber.org/atomic" "go.uber.org/zap" + fppath "github.com/babylonlabs-io/finality-provider/lib/math" + "github.com/babylonlabs-io/finality-provider/clientcontroller" "github.com/babylonlabs-io/finality-provider/eotsmanager" fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config" @@ -693,12 +694,15 @@ func (fp *FinalityProviderInstance) SubmitFinalitySignature(b *types.BlockInfo) return nil, fmt.Errorf("failed to send finality signature to the consumer chain: %w", err) } - // update DB - fp.MustUpdateStateAfterFinalitySigSubmission(b.Height) + // it is possible that the vote is duplicate so the metrics do need to update + if res.TxHash != "" { + // update DB + fp.MustUpdateStateAfterFinalitySigSubmission(b.Height) - // update metrics - fp.metrics.RecordFpVoteTime(fp.GetBtcPkHex()) - fp.metrics.IncrementFpTotalVotedBlocks(fp.GetBtcPkHex()) + // update metrics + fp.metrics.RecordFpVoteTime(fp.GetBtcPkHex()) + fp.metrics.IncrementFpTotalVotedBlocks(fp.GetBtcPkHex()) + } return res, nil } @@ -785,6 +789,10 @@ func (fp *FinalityProviderInstance) TestSubmitFinalitySignatureAndExtractPrivKey return nil, nil, fmt.Errorf("failed to send finality signature to the consumer chain: %w", err) } + if res.TxHash == "" { + return res, nil, nil + } + // try to extract the private key var privKey *btcec.PrivateKey for _, ev := range res.Events { diff --git a/itest/e2e_test.go b/itest/e2e_test.go index e2587783..10d41c68 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -4,17 +4,19 @@ package e2etest import ( + "math/rand" + "testing" + "time" + sdkmath "cosmossdk.io/math" "github.com/babylonlabs-io/babylon/testutil/datagen" - "github.com/babylonlabs-io/finality-provider/clientcontroller" - "github.com/babylonlabs-io/finality-provider/finality-provider/cmd/fpd/daemon" - "github.com/babylonlabs-io/finality-provider/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/stretchr/testify/require" "go.uber.org/zap" - "math/rand" - "testing" - "time" + + "github.com/babylonlabs-io/finality-provider/clientcontroller" + "github.com/babylonlabs-io/finality-provider/finality-provider/cmd/fpd/daemon" + "github.com/babylonlabs-io/finality-provider/types" ) var ( @@ -84,6 +86,13 @@ func TestDoubleSigning(t *testing.T) { finalizedBlocks := tm.WaitForNFinalizedBlocks(t, 1) + // test duplicate vote which should be ignored + res, extractedKey, err := fpIns.TestSubmitFinalitySignatureAndExtractPrivKey(finalizedBlocks[0]) + require.NoError(t, err) + require.Nil(t, extractedKey) + require.Empty(t, res) + t.Logf("duplicate vote for %d is sent", finalizedBlocks[0].Height) + // attack: manually submit a finality vote over a conflicting block // to trigger the extraction of finality-provider's private key r := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -91,19 +100,13 @@ func TestDoubleSigning(t *testing.T) { Height: finalizedBlocks[0].Height, Hash: datagen.GenRandomByteArray(r, 32), } - _, extractedKey, err := fpIns.TestSubmitFinalitySignatureAndExtractPrivKey(b) + _, extractedKey, err = fpIns.TestSubmitFinalitySignatureAndExtractPrivKey(b) require.NoError(t, err) require.NotNil(t, extractedKey) localKey := tm.GetFpPrivKey(t, fpIns.GetBtcPkBIP340().MustMarshal()) require.True(t, localKey.Key.Equals(&extractedKey.Key) || localKey.Key.Negate().Equals(&extractedKey.Key)) t.Logf("the equivocation attack is successful") - - tm.WaitForFpShutDown(t) - - // try to start the finality providers and the slashed one should expect err - err = tm.Fpa.StartHandlingFinalityProvider(fpIns.GetBtcPkBIP340(), "") - require.Error(t, err) } // TestFastSync tests the fast sync process where the finality-provider is terminated and restarted with fast sync