diff --git a/eotsmanager/proto/eotsmanager.pb.go b/eotsmanager/proto/eotsmanager.pb.go index eaf5ac07..50c63822 100644 --- a/eotsmanager/proto/eotsmanager.pb.go +++ b/eotsmanager/proto/eotsmanager.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: eotsmanager.proto diff --git a/finality-provider/cmd/fpd/daemon/daemon_commands.go b/finality-provider/cmd/fpd/daemon/daemon_commands.go index 4a982263..c8683e5c 100644 --- a/finality-provider/cmd/fpd/daemon/daemon_commands.go +++ b/finality-provider/cmd/fpd/daemon/daemon_commands.go @@ -8,6 +8,7 @@ import ( "strconv" "cosmossdk.io/math" + "github.com/babylonlabs-io/babylon/types" bbntypes "github.com/babylonlabs-io/babylon/types" "github.com/cosmos/cosmos-sdk/client" sdkflags "github.com/cosmos/cosmos-sdk/client/flags" @@ -66,6 +67,12 @@ func CommandCreateFP() *cobra.Command { Use: "create-finality-provider", Aliases: []string{"cfp"}, Short: "Create a finality provider object and save it in database.", + Long: fmt.Sprintf(` + Create a new finality provider object and store it in the finality provider database. + It needs to have an operating EOTS manager available and running. + + If the flag %s is set, it will ask for the key record from the EOTS manager for the + corresponding EOTS public key. If it is not set, it will create a new EOTS key`, fpEotsPkFlag), Example: fmt.Sprintf(`fpd create-finality-provider --daemon-address %s ...`, defaultFpdDaemonAddress), Args: cobra.NoArgs, RunE: fpcmd.RunEWithClientCtx(runCommandCreateFP), @@ -84,6 +91,7 @@ func CommandCreateFP() *cobra.Command { f.String(websiteFlag, "", "An optional website link") f.String(securityContactFlag, "", "An email for security contact") f.String(detailsFlag, "", "Other optional details") + f.String(fpEotsPkFlag, "", "Optional hex EOTS public key, if not provided a new one will be created") return cmd } @@ -135,10 +143,24 @@ func runCommandCreateFP(ctx client.Context, cmd *cobra.Command, _ []string) erro return fmt.Errorf("failed to read flag %s: %w", hdPathFlag, err) } + eotsPkHex, err := flags.GetString(fpEotsPkFlag) + if err != nil { + return fmt.Errorf("failed to read flag %s: %w", fpEotsPkFlag, err) + } + + if len(eotsPkHex) > 0 { + // if is set, validate before the creation request + _, err := types.NewBIP340PubKeyFromHex(eotsPkHex) + if err != nil { + return fmt.Errorf("invalid eots public key %s: %w", eotsPkHex, err) + } + } + info, err := client.CreateFinalityProvider( context.Background(), keyName, chainId, + eotsPkHex, passphrase, hdPath, description, diff --git a/finality-provider/cmd/fpd/daemon/init.go b/finality-provider/cmd/fpd/daemon/init.go index 3ccea218..99bb759a 100644 --- a/finality-provider/cmd/fpd/daemon/init.go +++ b/finality-provider/cmd/fpd/daemon/init.go @@ -36,7 +36,7 @@ func runInitCmd(ctx client.Context, cmd *cobra.Command, args []string) error { homePath = util.CleanAndExpandPath(homePath) force, err := cmd.Flags().GetBool(forceFlag) if err != nil { - return fmt.Errorf("failed to read flag %s: %w", fpEotsPkFlag, err) + return fmt.Errorf("failed to read flag %s: %w", forceFlag, err) } if util.FileExists(homePath) && !force { diff --git a/finality-provider/proto/finality_providers.pb.go b/finality-provider/proto/finality_providers.pb.go index 7aa726ad..760750c9 100644 --- a/finality-provider/proto/finality_providers.pb.go +++ b/finality-provider/proto/finality_providers.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.33.0 // protoc (unknown) // source: finality_providers.proto @@ -201,6 +201,10 @@ type CreateFinalityProviderRequest struct { Description []byte `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` // commission defines the commission rate for the finality provider Commission string `protobuf:"bytes,6,opt,name=commission,proto3" json:"commission,omitempty"` + // eots_pk_hex it is the optional EOTS public key and used to ask for + // the key record from the EOTS manager for the corresponding EOTS public key. + // If this property is not set, it will create a new EOTS key. + EotsPkHex string `protobuf:"bytes,7,opt,name=eots_pk_hex,json=eotsPkHex,proto3" json:"eots_pk_hex,omitempty"` } func (x *CreateFinalityProviderRequest) Reset() { @@ -277,6 +281,13 @@ func (x *CreateFinalityProviderRequest) GetCommission() string { return "" } +func (x *CreateFinalityProviderRequest) GetEotsPkHex() string { + if x != nil { + return x.EotsPkHex + } + return "" +} + type CreateFinalityProviderResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1299,7 +1310,7 @@ var file_finality_providers_proto_rawDesc = []byte{ 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xf5, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x95, 0x02, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, @@ -1314,7 +1325,9 @@ var file_finality_providers_proto_rawDesc = []byte{ 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x23, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, - 0x44, 0x65, 0x63, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x44, 0x65, 0x63, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1e, 0x0a, 0x0b, 0x65, 0x6f, 0x74, 0x73, 0x5f, 0x70, 0x6b, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6f, 0x74, 0x73, 0x50, 0x6b, 0x48, 0x65, 0x78, 0x22, 0x6a, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x11, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x72, diff --git a/finality-provider/proto/finality_providers.proto b/finality-provider/proto/finality_providers.proto index 73082b49..ac4a6a64 100644 --- a/finality-provider/proto/finality_providers.proto +++ b/finality-provider/proto/finality_providers.proto @@ -61,6 +61,10 @@ message CreateFinalityProviderRequest { (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; + // eots_pk_hex it is the optional EOTS public key and used to ask for + // the key record from the EOTS manager for the corresponding EOTS public key. + // If this property is not set, it will create a new EOTS key. + string eots_pk_hex = 7; } message CreateFinalityProviderResponse { diff --git a/finality-provider/service/app.go b/finality-provider/service/app.go index d86ccfea..8373b07c 100644 --- a/finality-provider/service/app.go +++ b/finality-provider/service/app.go @@ -351,6 +351,7 @@ func (app *FinalityProviderApp) Stop() error { func (app *FinalityProviderApp) CreateFinalityProvider( keyName, chainID, passPhrase, hdPath string, + eotsPk *bbntypes.BIP340PubKey, description *stakingtypes.Description, commission *sdkmath.LegacyDec, ) (*CreateFinalityProviderResult, error) { @@ -360,6 +361,7 @@ func (app *FinalityProviderApp) CreateFinalityProvider( chainID: chainID, passPhrase: passPhrase, hdPath: hdPath, + eotsPk: eotsPk, description: description, commission: commission, errResponse: make(chan error, 1), @@ -398,14 +400,18 @@ func (app *FinalityProviderApp) handleCreateFinalityProviderRequest(req *createF } // 2. create EOTS key - fpPkBytes, err := app.eotsManager.CreateKey(req.keyName, req.passPhrase, req.hdPath) - if err != nil { - return nil, err - } - fpPk, err := bbntypes.NewBIP340PubKey(fpPkBytes) - if err != nil { - return nil, err + fpPk := req.eotsPk + if req.eotsPk == nil { + fpPkBytes, err := app.eotsManager.CreateKey(req.keyName, req.passPhrase, req.hdPath) + if err != nil { + return nil, err + } + fpPk, err = bbntypes.NewBIP340PubKey(fpPkBytes) + if err != nil { + return nil, err + } } + fpRecord, err := app.eotsManager.KeyRecord(fpPk.MustMarshal(), req.passPhrase) if err != nil { return nil, fmt.Errorf("failed to get finality-provider record: %w", err) diff --git a/finality-provider/service/app_test.go b/finality-provider/service/app_test.go index 38ff9a5c..49bdeb46 100644 --- a/finality-provider/service/app_test.go +++ b/finality-provider/service/app_test.go @@ -76,8 +76,24 @@ func FuzzRegisterFinalityProvider(f *testing.F) { require.NoError(t, err) }() + var eotsPk *bbntypes.BIP340PubKey + eotsPk = nil + generateEotsKeyBefore := r.Int31n(10) > 5 + if generateEotsKeyBefore { + // sometimes uses the previously generated EOTS pk + eotsKeyName := testutil.GenRandomHexStr(r, 4) + eotsPkBz, err := em.CreateKey(eotsKeyName, passphrase, hdPath) + require.NoError(t, err) + eotsPk, err = bbntypes.NewBIP340PubKey(eotsPkBz) + require.NoError(t, err) + } + // create a finality-provider object and save it to db - fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath) + fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath, eotsPk) + if generateEotsKeyBefore { + require.Equal(t, eotsPk, bbntypes.NewBIP340PubKeyFromBTCPK(fp.BtcPk)) + } + btcSig := new(bbntypes.BIP340Signature) err = btcSig.Unmarshal(fp.Pop.BtcSig) require.NoError(t, err) diff --git a/finality-provider/service/client/rpcclient.go b/finality-provider/service/client/rpcclient.go index eda110c0..21bdc39f 100644 --- a/finality-provider/service/client/rpcclient.go +++ b/finality-provider/service/client/rpcclient.go @@ -60,7 +60,7 @@ func (c *FinalityProviderServiceGRpcClient) RegisterFinalityProvider( func (c *FinalityProviderServiceGRpcClient) CreateFinalityProvider( ctx context.Context, - keyName, chainID, passphrase, hdPath string, + keyName, chainID, eotsPkHex, passphrase, hdPath string, description types.Description, commission *sdkmath.LegacyDec, ) (*proto.CreateFinalityProviderResponse, error) { @@ -77,6 +77,7 @@ func (c *FinalityProviderServiceGRpcClient) CreateFinalityProvider( HdPath: hdPath, Description: descBytes, Commission: commission.String(), + EotsPkHex: eotsPkHex, } res, err := c.client.CreateFinalityProvider(ctx, req) diff --git a/finality-provider/service/fp_instance_test.go b/finality-provider/service/fp_instance_test.go index 84437e77..75cb57b7 100644 --- a/finality-provider/service/fp_instance_test.go +++ b/finality-provider/service/fp_instance_test.go @@ -124,7 +124,7 @@ func startFinalityProviderAppWithRegisteredFp(t *testing.T, r *rand.Rand, cc cli require.NoError(t, err) // create registered finality-provider - fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath) + fp := testutil.GenStoredFinalityProvider(r, t, app, passphrase, hdPath, nil) pubRandProofStore := app.GetPubRandProofStore() fpStore := app.GetFinalityProviderStore() err = fpStore.SetFpStatus(fp.BtcPk, proto.FinalityProviderStatus_REGISTERED) diff --git a/finality-provider/service/fp_store_adapter.go b/finality-provider/service/fp_store_adapter.go index e5dc7bd0..356475f5 100644 --- a/finality-provider/service/fp_store_adapter.go +++ b/finality-provider/service/fp_store_adapter.go @@ -24,6 +24,7 @@ type createFinalityProviderRequest struct { passPhrase string hdPath string chainID string + eotsPk *bbntypes.BIP340PubKey description *stakingtypes.Description commission *sdkmath.LegacyDec errResponse chan error diff --git a/finality-provider/service/rpcserver.go b/finality-provider/service/rpcserver.go index f1196f27..b792491d 100644 --- a/finality-provider/service/rpcserver.go +++ b/finality-provider/service/rpcserver.go @@ -81,9 +81,10 @@ func (r *rpcServer) GetInfo(context.Context, *proto.GetInfoRequest) (*proto.GetI } // CreateFinalityProvider generates a finality-provider object and saves it in the database -func (r *rpcServer) CreateFinalityProvider(ctx context.Context, req *proto.CreateFinalityProviderRequest) ( - *proto.CreateFinalityProviderResponse, error) { - +func (r *rpcServer) CreateFinalityProvider( + ctx context.Context, + req *proto.CreateFinalityProviderRequest, +) (*proto.CreateFinalityProviderResponse, error) { commissionRate, err := math.LegacyNewDecFromStr(req.Commission) if err != nil { return nil, err @@ -94,11 +95,17 @@ func (r *rpcServer) CreateFinalityProvider(ctx context.Context, req *proto.Creat return nil, err } + eotsPk, err := parseOptEotsPk(req.EotsPkHex) + if err != nil { + return nil, err + } + result, err := r.app.CreateFinalityProvider( req.KeyName, req.ChainId, req.Passphrase, req.HdPath, + eotsPk, &description, &commissionRate, ) @@ -218,3 +225,10 @@ func (r *rpcServer) SignMessageFromChainKey(ctx context.Context, req *proto.Sign return &proto.SignMessageFromChainKeyResponse{Signature: signature}, nil } + +func parseOptEotsPk(eotsPkHex string) (*bbntypes.BIP340PubKey, error) { + if len(eotsPkHex) > 0 { + return bbntypes.NewBIP340PubKeyFromHex(eotsPkHex) + } + return nil, nil +} diff --git a/itest/test_manager.go b/itest/test_manager.go index 9aff6817..4a55469b 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -187,7 +187,7 @@ func StartManagerWithFinalityProvider(t *testing.T, n int) (*TestManager, []*ser err = tm.BabylonHandler.BabylonNode.TxBankSend(fpBbnKeyInfo.AccAddress.String(), "1000000ubbn") require.NoError(t, err) - res, err := app.CreateFinalityProvider(fpName, chainID, passphrase, hdPath, desc, &commission) + 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) diff --git a/testutil/datagen.go b/testutil/datagen.go index d9d5264f..4af9a291 100644 --- a/testutil/datagen.go +++ b/testutil/datagen.go @@ -16,6 +16,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" + bbntypes "github.com/babylonlabs-io/babylon/types" bstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" "github.com/cosmos/cosmos-sdk/client" @@ -104,7 +105,7 @@ func GenBlocks(r *rand.Rand, startHeight, endHeight uint64) []*types.BlockInfo { } // GenStoredFinalityProvider generates a random finality-provider from the keyring and store it in DB -func GenStoredFinalityProvider(r *rand.Rand, t *testing.T, app *service.FinalityProviderApp, passphrase, hdPath string) *store.StoredFinalityProvider { +func GenStoredFinalityProvider(r *rand.Rand, t *testing.T, app *service.FinalityProviderApp, passphrase, hdPath string, eotsPk *bbntypes.BIP340PubKey) *store.StoredFinalityProvider { // generate keyring keyName := GenRandomHexStr(r, 4) chainID := GenRandomHexStr(r, 4) @@ -113,7 +114,7 @@ func GenStoredFinalityProvider(r *rand.Rand, t *testing.T, app *service.Finality _, err := service.CreateChainKey(cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.ChainID, keyName, keyring.BackendTest, passphrase, hdPath, "") require.NoError(t, err) - res, err := app.CreateFinalityProvider(keyName, chainID, passphrase, hdPath, RandomDescription(r), ZeroCommissionRate()) + res, err := app.CreateFinalityProvider(keyName, chainID, passphrase, hdPath, eotsPk, RandomDescription(r), ZeroCommissionRate()) require.NoError(t, err) btcPk, err := bbn.NewBIP340PubKeyFromHex(res.FpInfo.BtcPkHex)