From 25a3ef5d6c1811a713bd7f1e148917f840187832 Mon Sep 17 00:00:00 2001 From: bap2pecs Date: Fri, 22 Nov 2024 21:06:24 +0800 Subject: [PATCH 1/5] init --- finality-provider/cmd/fpd/daemon/commit_pr.go | 105 ++++++++++++++++++ .../cmd/fpd/daemon/daemon_commands.go | 9 +- finality-provider/cmd/fpd/main.go | 1 + finality-provider/service/fp_instance.go | 98 +++++++++++++++- 4 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 finality-provider/cmd/fpd/daemon/commit_pr.go diff --git a/finality-provider/cmd/fpd/daemon/commit_pr.go b/finality-provider/cmd/fpd/daemon/commit_pr.go new file mode 100644 index 00000000..9b7ce927 --- /dev/null +++ b/finality-provider/cmd/fpd/daemon/commit_pr.go @@ -0,0 +1,105 @@ +package daemon + +import ( + "fmt" + "math" + "path/filepath" + "strconv" + + bbntypes "github.com/babylonlabs-io/babylon/types" + fpcc "github.com/babylonlabs-io/finality-provider/clientcontroller" + eotsclient "github.com/babylonlabs-io/finality-provider/eotsmanager/client" + fpcmd "github.com/babylonlabs-io/finality-provider/finality-provider/cmd" + fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config" + "github.com/babylonlabs-io/finality-provider/finality-provider/service" + "github.com/babylonlabs-io/finality-provider/finality-provider/store" + "github.com/babylonlabs-io/finality-provider/log" + "github.com/babylonlabs-io/finality-provider/metrics" + "github.com/babylonlabs-io/finality-provider/util" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +// CommandCommitPubRand returns the commit-pubrand command +func CommandCommitPubRand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "unsafe-commit-pubrand [fp-eots-pk-hex] [target-height]", + Aliases: []string{"unsafe-cpr"}, + Short: "[UNSAFE] Manually trigger public randomness commitment for a finality provider", + Long: `[UNSAFE] Manually trigger public randomness commitment for a finality provider. +WARNING: this can drain the finality provider's balance if the target height is too high.`, + Example: `fpd unsafe-commit-pubrand --home /home/user/.fpd [fp-eots-pk-hex] [target-height]`, + Args: cobra.ExactArgs(2), + RunE: fpcmd.RunEWithClientCtx(runCommandCommitPubRand), + } + cmd.Flags().Uint64("start-height", math.MaxUint64, "The block height to start committing pubrand from (optional)") + return cmd +} + +func runCommandCommitPubRand(ctx client.Context, cmd *cobra.Command, args []string) error { + fpPk, err := bbntypes.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + targetHeight, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + startHeight, err := cmd.Flags().GetUint64("start-height") + if err != nil { + return err + } + + // Get homePath from context like in start.go + clientCtx := client.GetClientContextFromCmd(cmd) + homePath, err := filepath.Abs(clientCtx.HomeDir) + if err != nil { + return err + } + homePath = util.CleanAndExpandPath(homePath) + + cfg, err := fpcfg.LoadConfig(homePath) + if err != nil { + return fmt.Errorf("failed to load configuration: %w", err) + } + + logger, err := log.NewRootLoggerWithFile(fpcfg.LogFile(homePath), cfg.LogLevel) + if err != nil { + return fmt.Errorf("failed to initialize the logger: %w", err) + } + + db, err := cfg.DatabaseConfig.GetDBBackend() + if err != nil { + return fmt.Errorf("failed to create db backend: %w", err) + } + + fpStore, err := store.NewFinalityProviderStore(db) + if err != nil { + return fmt.Errorf("failed to initiate finality provider store: %w", err) + } + pubRandStore, err := store.NewPubRandProofStore(db) + if err != nil { + return fmt.Errorf("failed to initiate public randomness store: %w", err) + } + cc, err := fpcc.NewClientController(cfg.ChainType, cfg.BabylonConfig, &cfg.BTCNetParams, logger) + if err != nil { + return fmt.Errorf("failed to create rpc client for the Babylon chain: %w", err) + } + em, err := eotsclient.NewEOTSManagerGRpcClient(cfg.EOTSManagerAddress) + if err != nil { + return fmt.Errorf("failed to create EOTS manager client: %w", err) + } + + fp, err := service.NewFinalityProviderInstance( + fpPk, cfg, fpStore, pubRandStore, cc, em, metrics.NewFpMetrics(), "", + make(chan<- *service.CriticalError), logger) + if err != nil { + return fmt.Errorf("failed to create finality-provider %s instance: %w", fpPk.MarshalHex(), err) + } + + if startHeight == math.MaxUint64 { + return fp.TestCommitPubRand(targetHeight) + } else { + return fp.TestCommitPubRandWithStartHeight(startHeight, targetHeight) + } +} diff --git a/finality-provider/cmd/fpd/daemon/daemon_commands.go b/finality-provider/cmd/fpd/daemon/daemon_commands.go index 28e897d4..69e1149b 100644 --- a/finality-provider/cmd/fpd/daemon/daemon_commands.go +++ b/finality-provider/cmd/fpd/daemon/daemon_commands.go @@ -393,10 +393,11 @@ func runCommandRegisterFP(cmd *cobra.Command, args []string) error { // CommandAddFinalitySig returns the add-finality-sig command by connecting to the fpd daemon. func CommandAddFinalitySig() *cobra.Command { var cmd = &cobra.Command{ - Use: "add-finality-sig [fp-eots-pk-hex] [block-height]", - Aliases: []string{"afs"}, - Short: "Send a finality signature to the consumer chain. This command should only be used for presentation/testing purposes", - Example: fmt.Sprintf(`fpd add-finality-sig --daemon-address %s`, defaultFpdDaemonAddress), + Use: "unsafe-add-finality-sig [fp-eots-pk-hex] [block-height]", + Aliases: []string{"unsafe-afs"}, + Short: "[UNSAFE] Send a finality signature to the consumer chain.", + Long: "[UNSAFE] Send a finality signature to the consumer chain. This command should only be used for presentation/testing purposes", + Example: fmt.Sprintf(`fpd unsafe-add-finality-sig [fp-eots-pk-hex] [block-height] --daemon-address %s`, defaultFpdDaemonAddress), Args: cobra.ExactArgs(2), RunE: runCommandAddFinalitySig, } diff --git a/finality-provider/cmd/fpd/main.go b/finality-provider/cmd/fpd/main.go index 6c9ca981..fcebe405 100644 --- a/finality-provider/cmd/fpd/main.go +++ b/finality-provider/cmd/fpd/main.go @@ -35,6 +35,7 @@ func main() { daemon.CommandInfoFP(), daemon.CommandRegisterFP(), daemon.CommandAddFinalitySig(), daemon.CommandExportFP(), daemon.CommandTxs(), daemon.CommandUnjailFP(), daemon.CommandEditFinalityDescription(), daemon.CommandVersion(), + daemon.CommandCommitPubRand(), ) if err := cmd.Execute(); err != nil { diff --git a/finality-provider/service/fp_instance.go b/finality-provider/service/fp_instance.go index ad2c6750..9b219be2 100644 --- a/finality-provider/service/fp_instance.go +++ b/finality-provider/service/fp_instance.go @@ -76,6 +76,22 @@ func NewFinalityProviderInstance( return nil, fmt.Errorf("the finality provider instance cannot be initiated with status %s", sfp.Status.String()) } + return newFinalityProviderInstanceFromStore(sfp, cfg, s, prStore, cc, em, metrics, passphrase, errChan, logger) +} + +// Helper function to create FinalityProviderInstance from store data +func newFinalityProviderInstanceFromStore( + sfp *store.StoredFinalityProvider, + cfg *fpcfg.Config, + s *store.FinalityProviderStore, + prStore *store.PubRandProofStore, + cc clientcontroller.ClientController, + em eotsmanager.EOTSManager, + metrics *metrics.FpMetrics, + passphrase string, + errChan chan<- *CriticalError, + logger *zap.Logger, +) (*FinalityProviderInstance, error) { return &FinalityProviderInstance{ btcPk: bbntypes.NewBIP340PubKeyFromBTCPK(sfp.BtcPk), fpState: newFpState(sfp, s), @@ -468,6 +484,9 @@ func (fp *FinalityProviderInstance) retryCommitPubRandUntilBlockFinalized(target // CommitPubRand generates a list of Schnorr rand pairs, // commits the public randomness for the managed finality providers, // and save the randomness pair to DB +// Note: +// - if there is no pubrand committed before, it will start from the tipHeight +// - if the tipHeight is too large, it will only commit fp.cfg.NumPubRand pairs func (fp *FinalityProviderInstance) CommitPubRand(tipHeight uint64) (*types.TxResponse, error) { lastCommittedHeight, err := fp.GetLastCommittedHeight() if err != nil { @@ -493,6 +512,12 @@ func (fp *FinalityProviderInstance) CommitPubRand(tipHeight uint64) (*types.TxRe return nil, nil } + return fp.commitPubRandPairs(startHeight) +} + +// it will commit fp.cfg.NumPubRand pairs of public randomness starting from startHeight +func (fp *FinalityProviderInstance) commitPubRandPairs(startHeight uint64) (*types.TxResponse, error) { + activationBlkHeight, err := fp.cc.QueryFinalityActivationBlockHeight() if err != nil { return nil, err @@ -532,12 +557,83 @@ func (fp *FinalityProviderInstance) CommitPubRand(tipHeight uint64) (*types.TxRe // Update metrics fp.metrics.RecordFpRandomnessTime(fp.GetBtcPkHex()) - fp.metrics.RecordFpLastCommittedRandomnessHeight(fp.GetBtcPkHex(), lastCommittedHeight) + fp.metrics.RecordFpLastCommittedRandomnessHeight(fp.GetBtcPkHex(), startHeight+numPubRand-1) fp.metrics.AddToFpTotalCommittedRandomness(fp.GetBtcPkHex(), float64(len(pubRandList))) return res, nil } +// TestCommitPubRand is exposed for devops/testing purpose to allow manual committing public randomness in cases +// where FP is stuck due to lack of public randomness. +// +// Note: +// - this function is similar to `CommitPubRand` but should not be used in the main pubrand submission loop. +// - it will always start from the last committed height + 1 +// - if targetBlockHeight is too large, it will commit multiple fp.cfg.NumPubRand pairs in a loop until reaching the targetBlockHeight +func (fp *FinalityProviderInstance) TestCommitPubRand(targetBlockHeight uint64) error { + var startHeight, lastCommittedHeight uint64 + + lastCommittedHeight, err := fp.GetLastCommittedHeight() + if err != nil { + return err + } + if lastCommittedHeight == uint64(0) { + // Note: it can also be the case that the finality-provider has committed 1 pubrand before (but in practice, we + // will never set cfg.NumPubRand to 1. so we can safely assume it has never committed before) + startHeight = 0 + } else if lastCommittedHeight < targetBlockHeight { + startHeight = lastCommittedHeight + 1 + } else { + return fmt.Errorf( + "finality provider has already committed pubrand to target block height (pk: %s, target: %d, last committed: %d)", + fp.GetBtcPkHex(), + targetBlockHeight, + lastCommittedHeight, + ) + } + + return fp.TestCommitPubRandWithStartHeight(startHeight, targetBlockHeight) +} + +// TestCommitPubRandWithStartHeight is exposed for devops/testing purpose to allow manual committing public randomness +// in cases where FP is stuck due to lack of public randomness. +func (fp *FinalityProviderInstance) TestCommitPubRandWithStartHeight(startHeight uint64, targetBlockHeight uint64) error { + if startHeight > targetBlockHeight { + return fmt.Errorf("start height should not be greater than target block height") + } + + var lastCommittedHeight uint64 + lastCommittedHeight, err := fp.GetLastCommittedHeight() + if err != nil { + return err + } + if lastCommittedHeight >= startHeight { + return fmt.Errorf( + "finality provider has already committed pubrand at the start height (pk: %s, startHeight: %d, lastCommittedHeight: %d)", + fp.GetBtcPkHex(), + startHeight, + lastCommittedHeight, + ) + } + + fp.logger.Info("Start committing pubrand from block height", zap.Uint64("start_height", startHeight)) + + // TODO: instead of sending multiple txs, a better way is to bundle all the commit messages into + // one like we do for batch finality signatures. see discussion https://bit.ly/3OmbjkN + for startHeight <= targetBlockHeight { + _, err = fp.commitPubRandPairs(startHeight) + if err != nil { + return err + } + lastCommittedHeight = startHeight + uint64(fp.cfg.NumPubRand) - 1 + startHeight = lastCommittedHeight + 1 + fp.logger.Info("Committed pubrand to block height", zap.Uint64("height", lastCommittedHeight)) + } + + // no error. success + return nil +} + // SubmitFinalitySignature builds and sends a finality signature over the given block to the consumer chain func (fp *FinalityProviderInstance) SubmitFinalitySignature(b *types.BlockInfo) (*types.TxResponse, error) { return fp.SubmitBatchFinalitySignatures([]*types.BlockInfo{b}) From 9bb810d4e55e7250b1afbebb088d6dd9bf5be845 Mon Sep 17 00:00:00 2001 From: bap2pecs Date: Fri, 22 Nov 2024 21:09:20 +0800 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0f1e6d..942b6c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Improvements * [149](https://github.com/babylonlabs-io/finality-provider/pull/149) Remove update of config after `fpd keys add` +* [153](https://github.com/babylonlabs-io/finality-provider/pull/153) Add `unsafe-commit-pubrand` command ## v0.12.0 From 03bdb35c6e0ef58c528b97fa1ec148880d007584 Mon Sep 17 00:00:00 2001 From: bap2pecs Date: Fri, 22 Nov 2024 21:53:26 +0800 Subject: [PATCH 3/5] fix lint --- finality-provider/cmd/fpd/daemon/commit_pr.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/finality-provider/cmd/fpd/daemon/commit_pr.go b/finality-provider/cmd/fpd/daemon/commit_pr.go index 9b7ce927..f39eb4f8 100644 --- a/finality-provider/cmd/fpd/daemon/commit_pr.go +++ b/finality-provider/cmd/fpd/daemon/commit_pr.go @@ -9,7 +9,6 @@ import ( bbntypes "github.com/babylonlabs-io/babylon/types" fpcc "github.com/babylonlabs-io/finality-provider/clientcontroller" eotsclient "github.com/babylonlabs-io/finality-provider/eotsmanager/client" - fpcmd "github.com/babylonlabs-io/finality-provider/finality-provider/cmd" fpcfg "github.com/babylonlabs-io/finality-provider/finality-provider/config" "github.com/babylonlabs-io/finality-provider/finality-provider/service" "github.com/babylonlabs-io/finality-provider/finality-provider/store" @@ -30,13 +29,13 @@ func CommandCommitPubRand() *cobra.Command { WARNING: this can drain the finality provider's balance if the target height is too high.`, Example: `fpd unsafe-commit-pubrand --home /home/user/.fpd [fp-eots-pk-hex] [target-height]`, Args: cobra.ExactArgs(2), - RunE: fpcmd.RunEWithClientCtx(runCommandCommitPubRand), + RunE: runCommandCommitPubRand, } cmd.Flags().Uint64("start-height", math.MaxUint64, "The block height to start committing pubrand from (optional)") return cmd } -func runCommandCommitPubRand(ctx client.Context, cmd *cobra.Command, args []string) error { +func runCommandCommitPubRand(cmd *cobra.Command, args []string) error { fpPk, err := bbntypes.NewBIP340PubKeyFromHex(args[0]) if err != nil { return err @@ -99,7 +98,6 @@ func runCommandCommitPubRand(ctx client.Context, cmd *cobra.Command, args []stri if startHeight == math.MaxUint64 { return fp.TestCommitPubRand(targetHeight) - } else { - return fp.TestCommitPubRandWithStartHeight(startHeight, targetHeight) } + return fp.TestCommitPubRandWithStartHeight(startHeight, targetHeight) } From 6d08f99c7913f021fc84a58602233e5e44f84cb0 Mon Sep 17 00:00:00 2001 From: bap2pecs Date: Thu, 28 Nov 2024 16:50:03 +0800 Subject: [PATCH 4/5] fix lint --- finality-provider/service/fp_instance.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/finality-provider/service/fp_instance.go b/finality-provider/service/fp_instance.go index 9b219be2..a9039005 100644 --- a/finality-provider/service/fp_instance.go +++ b/finality-provider/service/fp_instance.go @@ -517,7 +517,6 @@ func (fp *FinalityProviderInstance) CommitPubRand(tipHeight uint64) (*types.TxRe // it will commit fp.cfg.NumPubRand pairs of public randomness starting from startHeight func (fp *FinalityProviderInstance) commitPubRandPairs(startHeight uint64) (*types.TxResponse, error) { - activationBlkHeight, err := fp.cc.QueryFinalityActivationBlockHeight() if err != nil { return nil, err @@ -577,13 +576,8 @@ func (fp *FinalityProviderInstance) TestCommitPubRand(targetBlockHeight uint64) if err != nil { return err } - if lastCommittedHeight == uint64(0) { - // Note: it can also be the case that the finality-provider has committed 1 pubrand before (but in practice, we - // will never set cfg.NumPubRand to 1. so we can safely assume it has never committed before) - startHeight = 0 - } else if lastCommittedHeight < targetBlockHeight { - startHeight = lastCommittedHeight + 1 - } else { + + if lastCommittedHeight >= targetBlockHeight { return fmt.Errorf( "finality provider has already committed pubrand to target block height (pk: %s, target: %d, last committed: %d)", fp.GetBtcPkHex(), @@ -592,6 +586,14 @@ func (fp *FinalityProviderInstance) TestCommitPubRand(targetBlockHeight uint64) ) } + if lastCommittedHeight == uint64(0) { + // Note: it can also be the case that the finality-provider has committed 1 pubrand before (but in practice, we + // will never set cfg.NumPubRand to 1. so we can safely assume it has never committed before) + startHeight = 0 + } else { + startHeight = lastCommittedHeight + 1 + } + return fp.TestCommitPubRandWithStartHeight(startHeight, targetBlockHeight) } From c2db1874888c540620fe771c43699e2fb410a44e Mon Sep 17 00:00:00 2001 From: bap2pecs <111917526+bap2pecs@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:45:40 -0500 Subject: [PATCH 5/5] Update CHANGELOG.md Co-authored-by: Cirrus Gai --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d08bd178..11be050d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * [#149](https://github.com/babylonlabs-io/finality-provider/pull/149) Remove update of config after `fpd keys add` * [#148](https://github.com/babylonlabs-io/finality-provider/pull/148) Allow command `eotsd keys add` to use empty HD path to derive new key and use master private key. -* [153](https://github.com/babylonlabs-io/finality-provider/pull/153) Add `unsafe-commit-pubrand` command +* [#153](https://github.com/babylonlabs-io/finality-provider/pull/153) Add `unsafe-commit-pubrand` command * [#154](https://github.com/babylonlabs-io/finality-provider/pull/154) Use sign schnorr instead of getting private key from EOTS manager * [#167](https://github.com/babylonlabs-io/finality-provider/pull/167) Remove last processed height