From 0a7fd839d9c802f42428beec5748735164dcb780 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 15 Jan 2025 11:59:17 -0300 Subject: [PATCH] chore: remove opt to remote signer, should always use remote signer (#95) * chore: remove opt to remote signer, should always use remote signer * fix: test sign * chore: removed unnecessary funcs * chore: add #95 to changelog * chore: add back the TestManager * fix: lint removed unused var * chore: address pr comments * chore: add unlock wallet prior to send btc del * chore: removed option to set remote signer * chore: removed duplicated test with and without remote signer, all use the remote signer * chore: add func to get open port to start covenant emulator --- CHANGELOG.md | 2 + cmd/covd/start.go | 33 +------- config/config.go | 21 ++--- covenant/covenant_test.go | 83 ++++++++++++++---- docs/covenant-emulator-setup.md | 41 ++++----- docs/covenant-signer-setup.md | 92 ++++++++++---------- itest/e2e_test.go | 38 +-------- itest/test_manager.go | 124 ++++++++++++--------------- itest/utils.go | 63 +++++++++++++- keyring/keyring.go | 27 ------ keyring/keyringcontroller.go | 12 --- keyring/signer.go | 145 -------------------------------- 12 files changed, 260 insertions(+), 421 deletions(-) delete mode 100644 keyring/signer.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a668a38..15ac9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Improvements * [#83](https://github.com/babylonlabs-io/covenant-emulator/pull/83) covenant-signer: remove go.mod +* [#95](https://github.com/babylonlabs-io/covenant-emulator/pull/95) removed local signer option +as the covenant emulator should only connect to a remote signer ## v0.11.3 diff --git a/cmd/covd/start.go b/cmd/covd/start.go index 446d8a8..e608d83 100644 --- a/cmd/covd/start.go +++ b/cmd/covd/start.go @@ -5,7 +5,6 @@ import ( "path/filepath" covcfg "github.com/babylonlabs-io/covenant-emulator/config" - "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/log" "github.com/babylonlabs-io/covenant-emulator/remotesigner" "github.com/babylonlabs-io/covenant-emulator/util" @@ -23,11 +22,6 @@ var startCommand = cli.Command{ Usage: "Start the Covenant Emulator Daemon", Description: "Start the Covenant Emulator Daemon. Note that the Covenant key pair should be created beforehand", Flags: []cli.Flag{ - cli.StringFlag{ - Name: passphraseFlag, - Usage: "The pass phrase used to encrypt the keys", - Value: defaultPassphrase, - }, cli.StringFlag{ Name: homeFlag, Usage: "The path to the covenant home directory", @@ -59,20 +53,9 @@ func start(ctx *cli.Context) error { return fmt.Errorf("failed to create rpc client for the consumer chain: %w", err) } - pwd := ctx.String(passphraseFlag) - - var signer covenant.Signer - - if cfg.RemoteSignerEnabled { - signer, err = newRemoteSignerFromConfig(cfg) - if err != nil { - return fmt.Errorf("failed to create remote signer from config: %w", err) - } - } else { - signer, err = newSignerFromConfig(cfg, pwd) - if err != nil { - return fmt.Errorf("failed to create keyring signer from config: %w", err) - } + signer, err := newRemoteSignerFromConfig(cfg) + if err != nil { + return fmt.Errorf("failed to create remote signer from config: %w", err) } ce, err := covenant.NewCovenantEmulator(cfg, bbnClient, logger, signer) @@ -94,16 +77,6 @@ func start(ctx *cli.Context) error { return srv.RunUntilShutdown() } -func newSignerFromConfig(cfg *covcfg.Config, passphrase string) (covenant.Signer, error) { - return keyring.NewKeyringSigner( - cfg.BabylonConfig.ChainID, - cfg.BabylonConfig.Key, - cfg.BabylonConfig.KeyDirectory, - cfg.BabylonConfig.KeyringBackend, - passphrase, - ) -} - func newRemoteSignerFromConfig(cfg *covcfg.Config) (covenant.Signer, error) { return remotesigner.NewRemoteSigner(cfg.RemoteSigner), nil } diff --git a/config/config.go b/config/config.go index dc57336..b35c9a6 100644 --- a/config/config.go +++ b/config/config.go @@ -47,8 +47,6 @@ type Config struct { BabylonConfig *BBNConfig `group:"babylon" namespace:"babylon"` - RemoteSignerEnabled bool `long:"remote-signer-enabled" description:"if true, covenant will use the remote signer to sign transactions"` - RemoteSigner *RemoteSignerCfg `group:"remotesigner" namespace:"remotesigner"` } @@ -134,16 +132,15 @@ func DefaultConfigWithHomePath(homePath string) Config { metricsCfg := DefaultMetricsConfig() remoteSignerCfg := DefaultRemoteSignerConfig() cfg := Config{ - LogLevel: defaultLogLevel, - QueryInterval: defaultQueryInterval, - DelegationLimit: defaultDelegationLimit, - SigsBatchSize: defaultSigsBatchSize, - BitcoinNetwork: defaultBitcoinNetwork, - BTCNetParams: defaultBTCNetParams, - Metrics: &metricsCfg, - BabylonConfig: &bbnCfg, - RemoteSignerEnabled: false, - RemoteSigner: &remoteSignerCfg, + LogLevel: defaultLogLevel, + QueryInterval: defaultQueryInterval, + DelegationLimit: defaultDelegationLimit, + SigsBatchSize: defaultSigsBatchSize, + BitcoinNetwork: defaultBitcoinNetwork, + BTCNetParams: defaultBTCNetParams, + Metrics: &metricsCfg, + BabylonConfig: &bbnCfg, + RemoteSigner: &remoteSignerCfg, } if err := cfg.Validate(); err != nil { diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index fe7e265..edb9cab 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -1,6 +1,7 @@ package covenant_test import ( + "context" "encoding/hex" "fmt" "math/rand" @@ -23,7 +24,13 @@ import ( covcfg "github.com/babylonlabs-io/covenant-emulator/config" "github.com/babylonlabs-io/covenant-emulator/covenant" + signerCfg "github.com/babylonlabs-io/covenant-emulator/covenant-signer/config" + "github.com/babylonlabs-io/covenant-emulator/covenant-signer/keystore/cosmos" + signerMetrics "github.com/babylonlabs-io/covenant-emulator/covenant-signer/observability/metrics" + signerApp "github.com/babylonlabs-io/covenant-emulator/covenant-signer/signerapp" + "github.com/babylonlabs-io/covenant-emulator/covenant-signer/signerservice" "github.com/babylonlabs-io/covenant-emulator/keyring" + "github.com/babylonlabs-io/covenant-emulator/remotesigner" "github.com/babylonlabs-io/covenant-emulator/testutil" "github.com/babylonlabs-io/covenant-emulator/types" ) @@ -37,6 +44,65 @@ var net = &chaincfg.SimNetParams func FuzzAddCovenantSig(f *testing.F) { testutil.AddRandomSeedsToFuzzer(f, 10) + + // create a Covenant key pair in the keyring + covenantConfig := covcfg.DefaultConfig() + + covenantConfig.BabylonConfig.KeyDirectory = f.TempDir() + + signerConfig := signerCfg.DefaultConfig() + signerConfig.KeyStore.CosmosKeyStore.ChainID = covenantConfig.BabylonConfig.ChainID + signerConfig.KeyStore.CosmosKeyStore.KeyName = covenantConfig.BabylonConfig.Key + signerConfig.KeyStore.CosmosKeyStore.KeyringBackend = covenantConfig.BabylonConfig.KeyringBackend + signerConfig.KeyStore.CosmosKeyStore.KeyDirectory = covenantConfig.BabylonConfig.KeyDirectory + keyRetriever, err := cosmos.NewCosmosKeyringRetriever(signerConfig.KeyStore.CosmosKeyStore) + require.NoError(f, err) + + covKeyPair, err := keyRetriever.Kr.CreateChainKey( + passphrase, + hdPath, + ) + require.NoError(f, err) + require.NotNil(f, covKeyPair) + + app := signerApp.NewSignerApp( + keyRetriever, + ) + + met := signerMetrics.NewCovenantSignerMetrics() + parsedConfig, err := signerConfig.Parse() + require.NoError(f, err) + + server, err := signerservice.New( + context.Background(), + parsedConfig, + app, + met, + ) + require.NoError(f, err) + + signer := remotesigner.NewRemoteSigner(covenantConfig.RemoteSigner) + + go func() { + _ = server.Start() + }() + + // Give some time to launch server + time.Sleep(time.Second) + + // unlock the signer before usage + err = signerservice.Unlock( + context.Background(), + covenantConfig.RemoteSigner.URL, + covenantConfig.RemoteSigner.Timeout, + passphrase, + ) + require.NoError(f, err) + + f.Cleanup(func() { + _ = server.Stop(context.TODO()) + }) + f.Fuzz(func(t *testing.T, seed int64) { t.Log("Seed", seed) r := rand.New(rand.NewSource(seed)) @@ -44,23 +110,6 @@ func FuzzAddCovenantSig(f *testing.F) { params := testutil.GenRandomParams(r, t) mockClientController := testutil.PrepareMockedClientController(t, params) - // create a Covenant key pair in the keyring - covenantConfig := covcfg.DefaultConfig() - covenantConfig.BabylonConfig.KeyDirectory = t.TempDir() - - covKeyPair, err := keyring.CreateCovenantKey( - covenantConfig.BabylonConfig.KeyDirectory, - covenantConfig.BabylonConfig.ChainID, - covenantConfig.BabylonConfig.Key, - covenantConfig.BabylonConfig.KeyringBackend, - passphrase, - hdPath, - ) - require.NoError(t, err) - - signer, err := keyring.NewKeyringSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) - require.NoError(t, err) - // create and start covenant emulator ce, err := covenant.NewCovenantEmulator(&covenantConfig, mockClientController, zap.NewNop(), signer) require.NoError(t, err) diff --git a/docs/covenant-emulator-setup.md b/docs/covenant-emulator-setup.md index 64f7df7..653fcf3 100644 --- a/docs/covenant-emulator-setup.md +++ b/docs/covenant-emulator-setup.md @@ -3,7 +3,7 @@ This document outlines the setup of the covenant-emulator daemon program. -## Table of Contents +## Table of Contents 1. [Prerequesites](#1-prerequisites) 2. [Install Covenant Emulator Binary](#2-install-covenant-emulator-binary) @@ -17,20 +17,20 @@ daemon program. To successfully complete this guide, you will need: -1. A running instance of the [covenant signer](../covenant-signer) - with the url that you configured it to. Please follow the - [covenant signer setup guide](./covenant-signer-setup.md) to +1. A running instance of the [covenant signer](../covenant-signer) + with the url that you configured it to. Please follow the + [covenant signer setup guide](./covenant-signer-setup.md) to complete the setup of the covenant signer with your keys before proceeding. Note that the phase-2 covenant-signer program is a different one than the one used doing phase-1 -2. A connection to a Babylon node. To run your own node, please refer to the +2. A connection to a Babylon node. To run your own node, please refer to the [Babylon Node Setup Guide](https://github.com/babylonlabs-io/networks/blob/main/bbn-test-5/bbn-test-5/babylon-node/README.md). ## 2. Install covenant emulator binary If you haven't already, download [Golang 1.23](https://go.dev/dl). -Once installed run: +Once installed, run: ```shell go version @@ -69,8 +69,8 @@ echo 'export PATH=$HOME/go/bin:$PATH' >> ~/.profile ### 3.1. Initialize directories -Next, we initialize the node and home directory. It should generate all of the -necessary files such as `covd.config`, these files will live in the `` +Next, initialize the node and home directory by generating all of the +necessary files such as `covd.conf`. These files will live in the `` that you set for the `--home` with the below command. ```shell @@ -87,9 +87,6 @@ $ ls ### 3.2. Configure the covenant emulator -As you have already set up the covenant signer, you can now configure the covenant -emulator to use it. - Use the following parameters to configure the `covd.conf` file. ``` @@ -127,9 +124,6 @@ URL = http://127.0.0.1:9792 ; client when making requests to the remote signer Timeout = 2s - -; if true, covenant will use the remote signer to sign transactions -RemoteSignerEnabled = true ``` Below are brief explanations of the configuration entries: @@ -144,15 +138,14 @@ Below are brief explanations of the configuration entries: - `KeyringBackend` - Storage backend for the keyring (os, file, kwallet, pass, test, memory) - `URL` - Endpoint where the remote signing service is running - `Timeout` - Maximum time to wait for remote signer responses -- `RemoteSignerEnabled` - Whether to use the remote signing service -Ensure that the covenant signer is running and unlocked before proceeding -otherwise you will be unable to run the emulator. +Ensure that the covenant signer is running and unlocked before proceeding. +Otherwise, you will be unable to run the emulator. ## 4. Generate key pairs -The covenant emulator daemon requires the existence of a Babylon keyring that -signs signatures and interacts with Babylon. Use the following command to generate +The covenant emulator daemon requires the existence of a Babylon keyring that +signs signatures and interacts with Babylon. Use the following command to generate the key: ```bash @@ -167,20 +160,20 @@ covd create-key --key-name --chain-id --keyring-backend **⚡ Note:** This program is a separate implementation from the @@ -40,20 +40,20 @@ Schnorr adaptor signatures required for covenant operations. This guide requires that: -1. You have a Bitcoin node setup to load your wallet and retrieve +1. You have a Bitcoin node setup to load your wallet and retrieve your master private key. 2. You have access to the private Bitcoin key you set up your covenant with. -3. A connection to a Babylon node. To run your own node, please refer to the +3. A connection to a Babylon node. To run your own node, please refer to the [Babylon Node Setup Guide](https://github.com/babylonlabs-io/networks/blob/main/bbn-test-5/babylon-node/README.md). -For a refresher on setting up the Bitcoin node, refer to the +For a refresher on setting up the Bitcoin node, refer to the [deployment guide of your phase-1 covenant signer setup](https://github.com/babylonlabs-io/covenant-signer/blob/main/docs/deployment.md#2-bitcoind-setup). ## 2. Shell Configuration -For security when entering sensitive commands, configure your shell to ignore +For security when entering sensitive commands, configure your shell to ignore commands that start with a space: For Bash users, please update if you are using a different shell. @@ -65,14 +65,14 @@ export HISTCONTROL=ignorespace # Then either restart your shell or run: source ~/.bashrc ``` -Please ensure that any commands that you wish to be hidden from your shell +Please ensure that any commands that you wish to be hidden from your shell history start with a space. ## 3. Installation If you haven't already, download [Golang 1.23](https://go.dev/dl). -Once installed run: +Once installed, run: ```shell go version @@ -150,16 +150,16 @@ Next, we are going to retrieve the `hdkeypath` of the Bitcoin address associated with our covenant key. We do this through the usage of the `getaddresssinfo` command -which takes your covenant Bitcoin address as a parameter. As mentioned above, +which takes your covenant Bitcoin address as a parameter. As mentioned above, you will need access to the Bitcoin key you set up your covenant with. ```shell - bitcoin-cli -datadir=./1/ getaddressinfo
| \ + bitcoin-cli -datadir=./1/ getaddressinfo
| \ jq '.hdkeypath | sub("^m/"; "") | sub("/[^/]+$"; "")' ``` -In the above command, we use the `jq` utility to extract only the relevant -`hdkeypath` information, which in this example is `84h/1h/0h/0/0` +In the above command, we use the `jq` utility to extract only the relevant +`hdkeypath` information, which in this example is `84h/1h/0h/0/0` (the initial `m/` can be ignored). Make note of the `hdkeypath` information - you'll need it later when @@ -168,19 +168,19 @@ deriving the covenant private key from the master key for verification purposes. #### Step 3: Retrieve the master private key In this step, -we are going to retrieve the **base58-encoded master private key** from the -Bitcoin wallet. This key will be used to derive the covenant private key, +we are going to retrieve the **base58-encoded master private key** from the +Bitcoin wallet. This key will be used to derive the covenant private key, which can then be imported directly into the Cosmos keyring. -The command below will list all descriptors in the wallet with private keys. -This will provide you with the descriptor needed to derive the private key. +The command below will list all descriptors in the wallet with private keys. +This will provide you with the descriptor needed to derive the private key. -Since Bitcoin wallets typically contain multiple descriptors +Since Bitcoin wallets typically contain multiple descriptors (usually 6 by default), we use `jq` to find the specific descriptor that -matches our previously saved `hdkeypath` in this example `(84h/1h/0h/0)` +matches our previously saved `hdkeypath` in this example `(84h/1h/0h/0)` and extract the master private key from it. -So, before you run this command you will need to replace the `` below +So, before you run this command you will need to replace the `` below with the one you retrieved in step 2. ```shell @@ -191,28 +191,28 @@ with the one you retrieved in step 2. ' ``` -The output will be: +The output will be: ```shell wpkh(tprv8ZgxMBicQKsPe9aCeUQgMEMy2YMZ6PHnn2iCuG12y5E8oYhYNEvUqUkNy6sJ7ViBmFUMicikHSK2LBUNPx5do5EDJBjG7puwd6azci2wEdq/84h/1h/0h/0/*)#sachkrde } ``` -As you can see above there is a concatenated string of your private key and +As you can see above there is a concatenated string of your private key and part of your `hdkeypath`. To extract the private key: 1. Remove everything outside the parentheses `wpkh(` and `)` -2. Remove the `hdkeypath` after the private key +2. Remove the `hdkeypath` after the private key (everything after and including `/`) -You'll be left with just the **base58-encoded master private key**, similar to +You'll be left with just the **base58-encoded master private key**, similar to below: ``` tprv8ZgxMBicQKsPe9aCeUQgMEMy2YMZ6PHnn2iCuG12y5E8oYhYNEvUqUkNy6sJ7ViBmFUMicikHSK2LBUNPx5do5EDJBjG7puwd6azci2wEdq ``` Now you have your **base58-encoded master private key**. -You can now pass the above information to the `covenant-signer` binary to +You can now pass the above information to the `covenant-signer` binary to derive the covenant private key from the master key using **BIP32 derivation**. Use the following command to derive the covenant private key: @@ -231,9 +231,9 @@ derived_public_key: 023a79b546c79d7f7c5ff20620d914b5cf7250631d12f6e26427ed9d3f98 ``` Parameters: -- ``: The base58-encoded master private key from your +- ``: The base58-encoded master private key from your Bitcoin wallet (first parameter) -- ``: The HD derivation path that specifies how to derive +- ``: The HD derivation path that specifies how to derive the child key (second parameter) To verify, you can execute the following: @@ -242,7 +242,7 @@ To verify, you can execute the following: bitcoin-cli getaddressinfo
| jq .publickey ``` -If the public key matches the `derived_public_key`s output from the +If the public key matches the `derived_public_key`s output from the `derive-child-key` command, the verification is successful. #### Step 4: Import the private key into a Cosmos Keyring @@ -267,9 +267,9 @@ to unlock the keyring. > **⚡ Note:** While both `os` and `file` backends are supported, the authors > of the docs have more thoroughly tested the `file` backend across > different environments. -> The `file` backend stores the private key in encrypted form -> on disk. When running `import-hex` with the `file` backend, you will be -> prompted for a passphrase. This passphrase will be required to unlock the +> The `file` backend stores the private key in encrypted form +> on disk. When running `import-hex` with the `file` backend, you will be +> prompted for a passphrase. This passphrase will be required to unlock the > signer later. To confirm that the import was successful, run: @@ -292,25 +292,25 @@ Congratulations! You have successfully imported your key. ## 5. Operation ### 5.1. Configuration -Next, we can return to the covenant signer directory -and create your own configuration file. Use the +Next, we can return to the covenant signer directory +and create your own configuration file. Use the following command to dump the configuration template: ```shell covenant-signer dump-cfg --config ``` -This will create a configuration file, from the example configuration, +This will create a configuration file, from the example configuration, in the specified path. -Replace the placeholder values with your own -configuration. This configuration can be placed directly in the +Replace the placeholder values with your own +configuration. This configuration can be placed directly in the `covenant-signer` directory. ```toml [keystore] -# Type of keystore to use for managing private keys. Currently only -# "cosmos" is supported, which uses the Cosmos SDK keyring system for +# Type of keystore to use for managing private keys. Currently only +# "cosmos" is supported, which uses the Cosmos SDK keyring system for # secure key storage. keystore-type = "cosmos" @@ -343,8 +343,8 @@ port = 2113 Below are brief explanations of the configuration entries: - `keystore-type`: Type of keystore used. Should be set to `"cosmos"` -- `key-directory`: Path where keys are stored. Do not include the keyring - backend type in the path (e.g., use `/path/to/keys` not +- `key-directory`: Path where keys are stored. Do not include the keyring + backend type in the path (e.g., use `/path/to/keys` not `/path/to/keys/keyring-file`). - `keyring-backend`: Backend system for key management, e.g., "file", "os". - `key-name`: Name of the key used for signing transactions. @@ -362,17 +362,17 @@ We will then run the following command to start the daemon: covenant-signer start --config ./path/to/config.toml ``` -The covenant signer must be run in a secure network and only accessible by the +The covenant signer must be run in a secure network and only accessible by the covenant emulator. -Once the covenant signer is set up and unlocked, you can configure the covenant +Once the covenant signer is set up and unlocked, you can configure the covenant emulator to use it. The URL of the covenant signer is configurable (`remotesigner` section) -but in this example we use the default value of +but in this example we use the default value of `http://127.0.0.1:9791`. ### 5.3. Unlocking the key -Before you can sign transactions with the covenant key, you must unlock the +Before you can sign transactions with the covenant key, you must unlock the keyring that stores it. This happens through a `POST` request on the `v1/unlock` endpoint with a payload containing the covenant keyring passphrase. @@ -381,8 +381,8 @@ the covenant keyring passphrase. curl -X POST http://127.0.0.1:9791/v1/unlock -d '{"passphrase": ""}' ``` -> ⚡ Note: Even if you provide the passphrase in the curl command to unlock the -> keyring, the CLI configuration for starting the service will still prompt you +> ⚡ Note: Even if you provide the passphrase in the curl command to unlock the +> keyring, the CLI configuration for starting the service will still prompt you > to enter the passphrase interactively. You can sign transactions by invoking the `v1/sign-transactions` endpoint, @@ -417,8 +417,8 @@ transactions and return it in JSON format. } ``` -These signatures can then be used to verify that the transactions were signed by +These signatures can then be used to verify that the transactions were signed by the covenant key. -Congratulations! You have successfully set up the covenant signer and are now able +Congratulations! You have successfully set up the covenant signer and are now able to sign transactions with the covenant key. diff --git a/itest/e2e_test.go b/itest/e2e_test.go index f575b7c..0ce387d 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -15,44 +15,8 @@ var ( stakingAmount = int64(20000) ) -// TestCovenantEmulatorLifeCycle tests the whole life cycle of a covenant emulator -// in two flows depending on whether the delegation is following pre-approval flow func TestCovenantEmulatorLifeCycle(t *testing.T) { - tm, btcPks := StartManagerWithFinalityProvider(t, 1, false) - defer tm.Stop(t) - - // send a BTC delegation that is not following pre-approval flow - _ = tm.InsertBTCDelegation(t, btcPks, stakingTime, stakingAmount, false) - - // check the BTC delegation is pending - _ = tm.WaitForNPendingDels(t, 1) - - // check the BTC delegation is active - _ = tm.WaitForNActiveDels(t, 1) - - // send a BTC delegation that is following pre-approval flow - _ = tm.InsertBTCDelegation(t, btcPks, stakingTime, stakingAmount, true) - - // check the BTC delegation is pending - _ = tm.WaitForNPendingDels(t, 1) - - time.Sleep(10 * time.Second) - - // check the BTC delegation is verified - dels := tm.WaitForNVerifiedDels(t, 1) - - // test duplicate, should expect no error - // remove covenant sigs - dels[0].CovenantSigs = nil - dels[0].BtcUndelegation.CovenantSlashingSigs = nil - dels[0].BtcUndelegation.CovenantUnbondingSigs = nil - res, err := tm.CovenantEmulator.AddCovenantSignatures(dels) - require.NoError(t, err) - require.Empty(t, res) -} - -func TestCovenantEmulatorLifeCycleWithRemoteSigner(t *testing.T) { - tm, btcPks := StartManagerWithFinalityProvider(t, 1, true) + tm, btcPks := StartManagerWithFinalityProvider(t, 1) defer tm.Stop(t) // send a BTC delegation that is not following pre-approval flow diff --git a/itest/test_manager.go b/itest/test_manager.go index 7158277..dd6b364 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -2,6 +2,7 @@ package e2etest import ( "context" + "fmt" "math/rand" "os" "sync" @@ -22,14 +23,12 @@ import ( signerApp "github.com/babylonlabs-io/covenant-emulator/covenant-signer/signerapp" "github.com/babylonlabs-io/covenant-emulator/covenant-signer/signerservice" signerService "github.com/babylonlabs-io/covenant-emulator/covenant-signer/signerservice" - covdkeyring "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/remotesigner" "github.com/babylonlabs-io/covenant-emulator/testutil" "github.com/babylonlabs-io/covenant-emulator/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" @@ -41,10 +40,9 @@ var ( eventuallyPollTime = 500 * time.Millisecond btcNetworkParams = &chaincfg.SimNetParams - covenantKeyName = "covenant-key" - chainID = "chain-test" - passphrase = "testpass" - hdPath = "" + chainID = "chain-test" + passphrase = "testpass" + hdPath = "" ) type TestManager struct { @@ -78,7 +76,7 @@ type testFinalityProviderData struct { PoP *bstypes.ProofOfPossessionBTC } -func StartManager(t *testing.T, useRemoteSigner bool) *TestManager { +func StartManager(t *testing.T) *TestManager { testDir, err := baseDir("cee2etest") require.NoError(t, err) @@ -88,76 +86,62 @@ func StartManager(t *testing.T, useRemoteSigner bool) *TestManager { require.NoError(t, err) // 1. prepare covenant key, which will be used as input of Babylon node - var signer covenant.Signer - var covPubKey *btcec.PublicKey - if useRemoteSigner { - covenantConfig.RemoteSignerEnabled = true - signerConfig := signerCfg.DefaultConfig() - signerConfig.KeyStore.CosmosKeyStore.ChainID = covenantConfig.BabylonConfig.ChainID - signerConfig.KeyStore.CosmosKeyStore.KeyName = covenantConfig.BabylonConfig.Key - signerConfig.KeyStore.CosmosKeyStore.KeyringBackend = covenantConfig.BabylonConfig.KeyringBackend - signerConfig.KeyStore.CosmosKeyStore.KeyDirectory = covenantConfig.BabylonConfig.KeyDirectory - keyRetriever, err := cosmos.NewCosmosKeyringRetriever(signerConfig.KeyStore.CosmosKeyStore) - require.NoError(t, err) - keyInfo, err := keyRetriever.Kr.CreateChainKey( - passphrase, - hdPath, - ) - require.NoError(t, err) - require.NotNil(t, keyInfo) + signerConfig := signerCfg.DefaultConfig() + signerConfig.KeyStore.CosmosKeyStore.ChainID = covenantConfig.BabylonConfig.ChainID + signerConfig.KeyStore.CosmosKeyStore.KeyName = covenantConfig.BabylonConfig.Key + signerConfig.KeyStore.CosmosKeyStore.KeyringBackend = covenantConfig.BabylonConfig.KeyringBackend + signerConfig.KeyStore.CosmosKeyStore.KeyDirectory = covenantConfig.BabylonConfig.KeyDirectory + keyRetriever, err := cosmos.NewCosmosKeyringRetriever(signerConfig.KeyStore.CosmosKeyStore) + require.NoError(t, err) + keyInfo, err := keyRetriever.Kr.CreateChainKey( + passphrase, + hdPath, + ) + require.NoError(t, err) + require.NotNil(t, keyInfo) - app := signerApp.NewSignerApp( - keyRetriever, - ) + app := signerApp.NewSignerApp( + keyRetriever, + ) - met := signerMetrics.NewCovenantSignerMetrics() - parsedConfig, err := signerConfig.Parse() - require.NoError(t, err) + met := signerMetrics.NewCovenantSignerMetrics() + parsedConfig, err := signerConfig.Parse() + require.NoError(t, err) - server, err := signerService.New( - context.Background(), - parsedConfig, - app, - met, - ) + remoteSignerPort, url := AllocateUniquePort(t) + parsedConfig.ServerConfig.Port = remoteSignerPort + covenantConfig.RemoteSigner.URL = fmt.Sprintf("http://%s", url) - require.NoError(t, err) + server, err := signerService.New( + context.Background(), + parsedConfig, + app, + met, + ) + require.NoError(t, err) - signer = remotesigner.NewRemoteSigner(covenantConfig.RemoteSigner) - covPubKey = keyInfo.PublicKey + signer := remotesigner.NewRemoteSigner(covenantConfig.RemoteSigner) + covPubKey := keyInfo.PublicKey - go func() { - _ = server.Start() - }() + go func() { + _ = server.Start() + }() - // Give some time to launch server - time.Sleep(3 * time.Second) + // Give some time to launch server + time.Sleep(3 * time.Second) - // unlock the signer before usage - err = signerservice.Unlock( - context.Background(), - covenantConfig.RemoteSigner.URL, - covenantConfig.RemoteSigner.Timeout, - passphrase, - ) - require.NoError(t, err) + // unlock the signer before usage + err = signerservice.Unlock( + context.Background(), + covenantConfig.RemoteSigner.URL, + covenantConfig.RemoteSigner.Timeout, + passphrase, + ) + require.NoError(t, err) - t.Cleanup(func() { - _ = server.Stop(context.TODO()) - }) - } else { - covKeyPair, err := covdkeyring.CreateCovenantKey(testDir, chainID, covenantKeyName, keyring.BackendTest, passphrase, hdPath) - require.NoError(t, err) - signer, err = covdkeyring.NewKeyringSigner( - covenantConfig.BabylonConfig.ChainID, - covenantConfig.BabylonConfig.Key, - covenantConfig.BabylonConfig.KeyDirectory, - covenantConfig.BabylonConfig.KeyringBackend, - passphrase, - ) - require.NoError(t, err) - covPubKey = covKeyPair.PublicKey - } + t.Cleanup(func() { + _ = server.Stop(context.TODO()) + }) // 2. prepare Babylon node bh := NewBabylonNodeHandler(t, bbntypes.NewBIP340PubKeyFromBTCPK(covPubKey)) @@ -203,8 +187,8 @@ func (tm *TestManager) WaitForServicesStart(t *testing.T) { t.Logf("Babylon node is started") } -func StartManagerWithFinalityProvider(t *testing.T, n int, useRemoteSigner bool) (*TestManager, []*btcec.PublicKey) { - tm := StartManager(t, useRemoteSigner) +func StartManagerWithFinalityProvider(t *testing.T, n int) (*TestManager, []*btcec.PublicKey) { + tm := StartManager(t) var btcPks []*btcec.PublicKey for i := 0; i < n; i++ { diff --git a/itest/utils.go b/itest/utils.go index ee51188..17bc5db 100644 --- a/itest/utils.go +++ b/itest/utils.go @@ -1,6 +1,67 @@ package e2etest -import "os" +import ( + "fmt" + mrand "math/rand/v2" + "net" + "os" + "sync" + "testing" +) + +// Track allocated ports, protected by a mutex +var ( + allocatedPorts = make(map[int]struct{}) + portMutex sync.Mutex +) + +// AllocateUniquePort tries to find an available TCP port on the localhost +// by testing multiple random ports within a specified range. +func AllocateUniquePort(t *testing.T) (int, string) { + randPort := func(base, spread int) int { + return base + mrand.IntN(spread) + } + + // Base port and spread range for port selection + const ( + basePort = 40000 + portRange = 50000 + ) + + var url string + // Try up to 10 times to find an available port + for i := 0; i < 30; i++ { + port := randPort(basePort, portRange) + + // Lock the mutex to check and modify the shared map + portMutex.Lock() + if _, exists := allocatedPorts[port]; exists { + // Port already allocated, try another one + portMutex.Unlock() + continue + } + + url = fmt.Sprintf("127.0.0.1:%d", port) + listener, err := net.Listen("tcp", url) + if err != nil { + portMutex.Unlock() + continue + } + + allocatedPorts[port] = struct{}{} + portMutex.Unlock() + + if err := listener.Close(); err != nil { + continue + } + + return port, url + } + + // If no available port was found, fail the test + t.Fatalf("failed to find an available port in range %d-%d", basePort, basePort+portRange) + return 0, "" +} func baseDir(pattern string) (string, error) { tempPath := os.TempDir() diff --git a/keyring/keyring.go b/keyring/keyring.go index efefdd8..cbcd7b2 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -1,42 +1,15 @@ package keyring import ( - "fmt" "os" "path" - "strings" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/babylonlabs-io/covenant-emulator/codec" "github.com/babylonlabs-io/covenant-emulator/types" ) -func CreateKeyring(keyringDir string, chainId string, backend string, input *strings.Reader) (keyring.Keyring, error) { - ctx, err := CreateClientCtx(keyringDir, chainId) - if err != nil { - return nil, err - } - - if backend == "" { - return nil, fmt.Errorf("the keyring backend should not be empty") - } - - kr, err := keyring.New( - ctx.ChainID, - backend, - ctx.KeyringDir, - input, - ctx.Codec, - ctx.KeyringOptions...) - if err != nil { - return nil, fmt.Errorf("failed to create keyring: %w", err) - } - - return kr, nil -} - func CreateClientCtx(keyringDir string, chainId string) (client.Context, error) { var err error var homeDir string diff --git a/keyring/keyringcontroller.go b/keyring/keyringcontroller.go index 5654116..78d7d06 100644 --- a/keyring/keyringcontroller.go +++ b/keyring/keyringcontroller.go @@ -53,18 +53,6 @@ func NewChainKeyringController(ctx client.Context, name, keyringBackend string) }, nil } -func NewChainKeyringControllerWithKeyring(kr keyring.Keyring, name string, input *strings.Reader) (*ChainKeyringController, error) { - if name == "" { - return nil, fmt.Errorf("the key name should not be empty") - } - - return &ChainKeyringController{ - kr: kr, - keyName: name, - input: input, - }, nil -} - func (kc *ChainKeyringController) GetKeyring() keyring.Keyring { return kc.kr } diff --git a/keyring/signer.go b/keyring/signer.go deleted file mode 100644 index a53aa6d..0000000 --- a/keyring/signer.go +++ /dev/null @@ -1,145 +0,0 @@ -package keyring - -import ( - "fmt" - "strings" - - "github.com/babylonlabs-io/babylon/btcstaking" - "github.com/babylonlabs-io/covenant-emulator/covenant" - - asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - secp "github.com/decred/dcrd/dcrec/secp256k1/v4" -) - -var _ covenant.Signer = KeyringSigner{} - -type KeyringSigner struct { - kc *ChainKeyringController - passphrase string -} - -func NewKeyringSigner(chainId, keyName, keyringDir, keyringBackend, passphrase string) (*KeyringSigner, error) { - input := strings.NewReader("") - kr, err := CreateKeyring(keyringDir, chainId, keyringBackend, input) - if err != nil { - return nil, fmt.Errorf("failed to create keyring: %w", err) - } - - kc, err := NewChainKeyringControllerWithKeyring(kr, keyName, input) - if err != nil { - return nil, err - } - - return &KeyringSigner{ - kc: kc, - passphrase: passphrase, - }, nil -} - -func (kcs KeyringSigner) PubKey() (*secp.PublicKey, error) { - record, err := kcs.kc.KeyRecord() - if err != nil { - return nil, err - } - - pubKey, err := record.GetPubKey() - if err != nil { - return nil, err - } - - return btcec.ParsePubKey(pubKey.Bytes()) -} - -// getPrivKey returns the keyring private key -// TODO: update btcstaking functions to avoid receiving private key as parameter -// and only sign it using the kcs.kc.GetKeyring().Sign() -func (kcs KeyringSigner) getPrivKey() (*btcec.PrivateKey, error) { - sdkPrivKey, err := kcs.kc.GetChainPrivKey(kcs.passphrase) - if err != nil { - return nil, err - } - - privKey, _ := btcec.PrivKeyFromBytes(sdkPrivKey.Key) - return privKey, nil -} - -// SignTransactions receives BTC delegation transactions to sign and returns all the signatures needed if nothing fails. -func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenant.SignaturesResponse, error) { - covenantPrivKey, err := kcs.getPrivKey() - if err != nil { - return nil, fmt.Errorf("failed to get Covenant private key: %w", err) - } - - slashSigs := make([][]byte, 0, len(req.FpEncKeys)) - slashUnbondingSigs := make([][]byte, 0, len(req.FpEncKeys)) - for _, fpEncKey := range req.FpEncKeys { - slashSig, slashUnbondingSig, err := slashUnbondSig(covenantPrivKey, req, fpEncKey) - if err != nil { - return nil, err - } - - slashSigs = append(slashSigs, slashSig.MustMarshal()) - slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) - } - - unbondingSig, err := unbondSig(covenantPrivKey, req) - if err != nil { - return nil, err - } - - return &covenant.SignaturesResponse{ - SlashSigs: slashSigs, - UnbondingSig: unbondingSig, - SlashUnbondingSigs: slashUnbondingSigs, - }, nil -} - -func slashUnbondSig( - covenantPrivKey *secp.PrivateKey, - signingTxReq covenant.SigningRequest, - fpEncKey *asig.EncryptionKey, -) (slashSig, slashUnbondingSig *asig.AdaptorSignature, err error) { - // creates slash sigs - slashSig, err = btcstaking.EncSignTxWithOneScriptSpendInputStrict( - signingTxReq.SlashingTx, - signingTxReq.StakingTx, - signingTxReq.StakingOutputIdx, - signingTxReq.SlashingPkScriptPath, - covenantPrivKey, - fpEncKey, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to sign adaptor slash signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) - } - - // creates slash unbonding sig - slashUnbondingSig, err = btcstaking.EncSignTxWithOneScriptSpendInputStrict( - signingTxReq.SlashUnbondingTx, - signingTxReq.UnbondingTx, - 0, // 0th output is always the unbonding script output - signingTxReq.UnbondingTxSlashingPkScriptPath, - covenantPrivKey, - fpEncKey, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to sign adaptor slash unbonding signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) - } - - return slashSig, slashUnbondingSig, nil -} - -func unbondSig(covenantPrivKey *secp.PrivateKey, signingTxReq covenant.SigningRequest) (*schnorr.Signature, error) { - unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - signingTxReq.UnbondingTx, - signingTxReq.StakingTx, - signingTxReq.StakingOutputIdx, - signingTxReq.StakingTxUnbondingPkScriptPath, - covenantPrivKey, - ) - if err != nil { - return nil, fmt.Errorf("failed to sign unbonding tx: %w", err) - } - return unbondingSig, nil -}