From a5fd0adce69f4666afad13be362fb5a9cbb489a2 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 13:52:59 +0100 Subject: [PATCH 01/14] Detach wallet coordination from the maintainer module --- cmd/flags.go | 36 ------- cmd/flags_test.go | 37 ------- cmd/maintainer.go | 1 - config/config_test.go | 20 ---- pkg/maintainer/config.go | 6 +- pkg/maintainer/maintainer.go | 13 --- pkg/maintainer/wallet/config.go | 19 ---- pkg/maintainer/wallet/deposit_sweep.go | 39 -------- pkg/maintainer/wallet/redemptions.go | 49 ---------- pkg/maintainer/wallet/wallet.go | 127 ------------------------- pkg/maintainer/wallet/wallet_test.go | 87 ----------------- 11 files changed, 2 insertions(+), 432 deletions(-) delete mode 100644 pkg/maintainer/wallet/config.go delete mode 100644 pkg/maintainer/wallet/wallet_test.go diff --git a/cmd/flags.go b/cmd/flags.go index 6add3cd7e0..ba0fb317d9 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -16,7 +16,6 @@ import ( chainEthereum "github.com/keep-network/keep-core/pkg/chain/ethereum" "github.com/keep-network/keep-core/pkg/clientinfo" "github.com/keep-network/keep-core/pkg/maintainer/spv" - "github.com/keep-network/keep-core/pkg/maintainer/wallet" "github.com/keep-network/keep-core/pkg/net/libp2p" "github.com/keep-network/keep-core/pkg/tbtc" ) @@ -327,41 +326,6 @@ func initMaintainerFlags(command *cobra.Command, cfg *config.Config) { "Disable Bitcoin difficulty proxy.", ) - command.Flags().BoolVar( - &cfg.Maintainer.WalletCoordination.Enabled, - "walletCoordination", - false, - "Start wallet coordination maintainer.", - ) - - command.Flags().DurationVar( - &cfg.Maintainer.WalletCoordination.RedemptionInterval, - "walletCoordination.redemptionInterval", - wallet.DefaultRedemptionInterval, - "The time interval in which pending redemptions requests are checked.", - ) - - command.Flags().Uint16Var( - &cfg.Maintainer.WalletCoordination.RedemptionWalletsLimit, - "walletCoordination.redemptionWalletsLimit", - wallet.DefaultRedemptionWalletsLimit, - "Limits the number of wallets that can receive a redemption proposal in the same time.", - ) - - command.Flags().Uint64Var( - &cfg.Maintainer.WalletCoordination.RedemptionRequestAmountLimit, - "walletCoordination.redemptionRequestAmountLimit", - wallet.DefaultRedemptionRequestAmountLimit, - "Limits the redemption requests to the ones below the given satoshi value.", - ) - - command.Flags().DurationVar( - &cfg.Maintainer.WalletCoordination.DepositSweepInterval, - "walletCoordination.depositSweepInterval", - wallet.DefaultDepositSweepInterval, - "The time interval in which unswept deposits are checked.", - ) - command.Flags().BoolVar( &cfg.Maintainer.Spv.Enabled, "spv", diff --git a/cmd/flags_test.go b/cmd/flags_test.go index 72af1f6c97..fc55031f36 100644 --- a/cmd/flags_test.go +++ b/cmd/flags_test.go @@ -239,43 +239,6 @@ var cmdFlagsTests = map[string]struct { expectedValueFromFlag: true, defaultValue: false, }, - "maintainer.walletCoordination": { - readValueFunc: func(c *config.Config) interface{} { return c.Maintainer.WalletCoordination.Enabled }, - flagName: "--walletCoordination", - flagValue: "", // don't provide any value - expectedValueFromFlag: true, - defaultValue: false, - }, - "maintainer.walletCoordination.redemptionInterval": { - readValueFunc: func(c *config.Config) interface{} { return c.Maintainer.WalletCoordination.RedemptionInterval }, - flagName: "--walletCoordination.redemptionInterval", - flagValue: "7h", - expectedValueFromFlag: 7 * time.Hour, - defaultValue: 3 * time.Hour, - }, - "maintainer.walletCoordination.redemptionWalletsLimit": { - readValueFunc: func(c *config.Config) interface{} { return c.Maintainer.WalletCoordination.RedemptionWalletsLimit }, - flagName: "--walletCoordination.redemptionWalletsLimit", - flagValue: "10", - expectedValueFromFlag: uint16(10), - defaultValue: uint16(3), - }, - "maintainer.walletCoordination.redemptionRequestAmountLimit": { - readValueFunc: func(c *config.Config) interface{} { - return c.Maintainer.WalletCoordination.RedemptionRequestAmountLimit - }, - flagName: "--walletCoordination.redemptionRequestAmountLimit", - flagValue: "500", - expectedValueFromFlag: uint64(500), - defaultValue: uint64(10 * 1e8), - }, - "maintainer.walletCoordination.depositSweepInterval": { - readValueFunc: func(c *config.Config) interface{} { return c.Maintainer.WalletCoordination.DepositSweepInterval }, - flagName: "--walletCoordination.depositSweepInterval", - flagValue: "35h", - expectedValueFromFlag: 35 * time.Hour, - defaultValue: 48 * time.Hour, - }, "maintainer.spv": { readValueFunc: func(c *config.Config) interface{} { return c.Maintainer.Spv.Enabled }, flagName: "--spv", diff --git a/cmd/maintainer.go b/cmd/maintainer.go index b7cc573012..b8d81e2aa5 100644 --- a/cmd/maintainer.go +++ b/cmd/maintainer.go @@ -78,7 +78,6 @@ func maintainers(cmd *cobra.Command, args []string) error { btcChain, btcDiffChain, tbtcChain, - tbtcChain, ) <-ctx.Done() diff --git a/config/config_test.go b/config/config_test.go index 0a02974ad1..8371f3d170 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -207,26 +207,6 @@ func TestReadConfigFromFile(t *testing.T) { readValueFunc: func(c *Config) interface{} { return c.Maintainer.BitcoinDifficulty.DisableProxy }, expectedValue: true, }, - "Maintainer.WalletCoordination.Enabled": { - readValueFunc: func(c *Config) interface{} { return c.Maintainer.WalletCoordination.Enabled }, - expectedValue: true, - }, - "Maintainer.WalletCoordination.RedemptionInterval": { - readValueFunc: func(c *Config) interface{} { return c.Maintainer.WalletCoordination.RedemptionInterval }, - expectedValue: 13 * time.Hour, - }, - "Maintainer.WalletCoordination.RedemptionWalletsLimit": { - readValueFunc: func(c *Config) interface{} { return c.Maintainer.WalletCoordination.RedemptionWalletsLimit }, - expectedValue: uint16(10), - }, - "Maintainer.WalletCoordination.RedemptionRequestAmountLimit": { - readValueFunc: func(c *Config) interface{} { return c.Maintainer.WalletCoordination.RedemptionRequestAmountLimit }, - expectedValue: uint64(500), - }, - "Maintainer.WalletCoordination.DepositSweepInterval": { - readValueFunc: func(c *Config) interface{} { return c.Maintainer.WalletCoordination.DepositSweepInterval }, - expectedValue: 64 * time.Hour, - }, "Maintainer.Spv.Enabled": { readValueFunc: func(c *Config) interface{} { return c.Maintainer.Spv.Enabled }, expectedValue: true, diff --git a/pkg/maintainer/config.go b/pkg/maintainer/config.go index 6616018240..52175cf350 100644 --- a/pkg/maintainer/config.go +++ b/pkg/maintainer/config.go @@ -3,12 +3,10 @@ package maintainer import ( "github.com/keep-network/keep-core/pkg/maintainer/btcdiff" "github.com/keep-network/keep-core/pkg/maintainer/spv" - "github.com/keep-network/keep-core/pkg/maintainer/wallet" ) // Config contains maintainer configuration. type Config struct { - BitcoinDifficulty btcdiff.Config - WalletCoordination wallet.Config - Spv spv.Config + BitcoinDifficulty btcdiff.Config + Spv spv.Config } diff --git a/pkg/maintainer/maintainer.go b/pkg/maintainer/maintainer.go index 9eca65468d..ea2fbfabcf 100644 --- a/pkg/maintainer/maintainer.go +++ b/pkg/maintainer/maintainer.go @@ -2,13 +2,11 @@ package maintainer import ( "context" - "github.com/ipfs/go-log/v2" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/maintainer/btcdiff" "github.com/keep-network/keep-core/pkg/maintainer/spv" - "github.com/keep-network/keep-core/pkg/maintainer/wallet" ) var logger = log.Logger("keep-maintainer") @@ -18,13 +16,11 @@ func Initialize( config Config, btcChain bitcoin.Chain, btcDiffChain btcdiff.Chain, - coordinatorChain wallet.Chain, spvChain spv.Chain, ) { // If none of the maintainers was specified in the config (i.e. no option was // provided to the `maintainer` command), all maintainers should be launched. launchAll := !config.BitcoinDifficulty.Enabled && - !config.WalletCoordination.Enabled && !config.Spv.Enabled if launchAll { @@ -40,15 +36,6 @@ func Initialize( ) } - if config.WalletCoordination.Enabled || launchAll { - wallet.Initialize( - ctx, - config.WalletCoordination, - coordinatorChain, - btcChain, - ) - } - if config.Spv.Enabled || launchAll { spv.Initialize( ctx, diff --git a/pkg/maintainer/wallet/config.go b/pkg/maintainer/wallet/config.go deleted file mode 100644 index 3d46ca6730..0000000000 --- a/pkg/maintainer/wallet/config.go +++ /dev/null @@ -1,19 +0,0 @@ -package wallet - -import "time" - -const ( - DefaultRedemptionInterval = 3 * time.Hour - DefaultRedemptionWalletsLimit = 3 - DefaultRedemptionRequestAmountLimit = uint64(10 * 1e8) // 10 BTC - DefaultDepositSweepInterval = 48 * time.Hour -) - -// Config holds configurable properties. -type Config struct { - Enabled bool - RedemptionInterval time.Duration - RedemptionWalletsLimit uint16 - RedemptionRequestAmountLimit uint64 - DepositSweepInterval time.Duration -} diff --git a/pkg/maintainer/wallet/deposit_sweep.go b/pkg/maintainer/wallet/deposit_sweep.go index 46f1e8c57d..4c421d7b85 100644 --- a/pkg/maintainer/wallet/deposit_sweep.go +++ b/pkg/maintainer/wallet/deposit_sweep.go @@ -1,7 +1,6 @@ package wallet import ( - "context" "fmt" "math" "math/big" @@ -14,44 +13,6 @@ import ( const depositScriptByteSize = 92 -func (wm *walletMaintainer) runDepositSweepTask(ctx context.Context) error { - depositSweepMaxSize, err := wm.chain.GetDepositSweepMaxSize() - if err != nil { - return fmt.Errorf("failed to get deposit sweep max size: [%w]", err) - } - - walletPublicKeyHash, deposits, err := FindDepositsToSweep( - wm.chain, - wm.btcChain, - [20]byte{}, - depositSweepMaxSize, - ) - if err != nil { - return fmt.Errorf("failed to prepare deposits sweep proposal: [%w]", err) - } - - if len(deposits) == 0 { - logger.Info("no deposits to sweep") - return nil - } - - return wm.runIfWalletUnlocked( - ctx, - walletPublicKeyHash, - tbtc.ActionDepositSweep, - func() error { - return ProposeDepositsSweep( - wm.chain, - wm.btcChain, - walletPublicKeyHash, - 0, - deposits, - false, - ) - }, - ) -} - // DepositReference holds some data allowing to identify and refer to a deposit. type DepositReference struct { FundingTxHash bitcoin.Hash diff --git a/pkg/maintainer/wallet/redemptions.go b/pkg/maintainer/wallet/redemptions.go index b7628e860a..1096b5f553 100644 --- a/pkg/maintainer/wallet/redemptions.go +++ b/pkg/maintainer/wallet/redemptions.go @@ -1,7 +1,6 @@ package wallet import ( - "context" "encoding/hex" "fmt" "math/big" @@ -14,54 +13,6 @@ import ( "github.com/keep-network/keep-core/pkg/tbtc" ) -func (wm *walletMaintainer) runRedemptionTask(ctx context.Context) error { - redemptionMaxSize, err := wm.chain.GetRedemptionMaxSize() - if err != nil { - return fmt.Errorf("failed to get redemption max size: [%w]", err) - } - - walletsPendingRedemptions, err := FindPendingRedemptions( - wm.chain, - PendingRedemptionsFilter{ - WalletPublicKeyHashes: nil, - WalletsLimit: wm.config.RedemptionWalletsLimit, - RequestsLimit: redemptionMaxSize, - RequestAmountLimit: wm.config.RedemptionRequestAmountLimit, - }, - ) - if err != nil { - return fmt.Errorf("failed to find pending redemption requests: [%w]", err) - } - - if len(walletsPendingRedemptions) == 0 { - logger.Info("no pending redemption requests") - return nil - } - - for walletPublicKeyHash, redeemersOutputScripts := range walletsPendingRedemptions { - err = wm.runIfWalletUnlocked( - ctx, - walletPublicKeyHash, - tbtc.ActionRedemption, - func() error { - return ProposeRedemption( - wm.chain, - wm.btcChain, - walletPublicKeyHash, - 0, - redeemersOutputScripts, - false, - ) - }, - ) - if err != nil { - return err - } - } - - return nil -} - // RedemptionRequest represents a redemption request. type RedemptionRequest struct { WalletPublicKeyHash [20]byte diff --git a/pkg/maintainer/wallet/wallet.go b/pkg/maintainer/wallet/wallet.go index 010eff78c7..0535528ea0 100644 --- a/pkg/maintainer/wallet/wallet.go +++ b/pkg/maintainer/wallet/wallet.go @@ -1,134 +1,7 @@ package wallet import ( - "context" - "fmt" - - "time" - "github.com/ipfs/go-log/v2" - - "github.com/keep-network/keep-core/internal/hexutils" - "github.com/keep-network/keep-core/pkg/bitcoin" - "github.com/keep-network/keep-core/pkg/tbtc" ) var logger = log.Logger("keep-maintainer-wallet") - -type walletMaintainer struct { - config Config - chain Chain - btcChain bitcoin.Chain -} - -// Initialize and start Wallet Coordination Maintainer. -func Initialize( - parentCtx context.Context, - config Config, - chain Chain, - btcChain bitcoin.Chain, -) { - if config.RedemptionInterval == 0 { - config.RedemptionInterval = DefaultRedemptionInterval - } - if config.RedemptionWalletsLimit == 0 { - config.RedemptionWalletsLimit = DefaultRedemptionWalletsLimit - } - if config.RedemptionRequestAmountLimit == 0 { - config.RedemptionRequestAmountLimit = DefaultRedemptionRequestAmountLimit - } - if config.DepositSweepInterval == 0 { - config.DepositSweepInterval = DefaultDepositSweepInterval - } - - wm := &walletMaintainer{ - config: config, - chain: chain, - btcChain: btcChain, - } - - go wm.startControlLoop(parentCtx) -} - -// startControlLoop starts the loop responsible for controlling the wallet -// coordination maintainer. -func (wm *walletMaintainer) startControlLoop(ctx context.Context) { - logger.Info("starting wallet coordination maintainer") - defer logger.Info("stopping wallet coordination maintainer") - - initialRedemptionDelay := 5 * time.Second - initialDepositSweepDelay := 60 * time.Second - - redemptionTicker := time.NewTicker(initialRedemptionDelay) - defer redemptionTicker.Stop() - - depositSweepTicker := time.NewTicker(initialDepositSweepDelay) - defer depositSweepTicker.Stop() - - logger.Infof("waiting [%s] until redemption task execution", initialRedemptionDelay) - logger.Infof("waiting [%s] until deposit sweep task execution", initialDepositSweepDelay) - - for { - select { - case <-ctx.Done(): - return - case <-redemptionTicker.C: - // Set the ticker to the expected interval. - redemptionTicker.Reset(wm.config.RedemptionInterval) - - logger.Info("starting redemption task execution...") - - if err := wm.runRedemptionTask(ctx); err != nil { - logger.Errorf("failed to run redemption task: [%v]", err) - } - - logger.Infof( - "redemption task run completed; next run in [%s]", - wm.config.RedemptionInterval, - ) - case <-depositSweepTicker.C: - // Set the ticker to the expected interval. - depositSweepTicker.Reset(wm.config.DepositSweepInterval) - - logger.Info("starting deposit sweep task execution...") - - if err := wm.runDepositSweepTask(ctx); err != nil { - logger.Errorf("failed to run deposit sweep task: [%v]", err) - } - - logger.Infof( - "deposit sweep task run completed; next run in [%s]", - wm.config.DepositSweepInterval, - ) - } - } -} - -func (wm *walletMaintainer) runIfWalletUnlocked( - ctx context.Context, - walletPublicKeyHash [20]byte, - currentWalletAction tbtc.WalletActionType, - runFunc func() error, -) error { - lockExpiration, lockWalletAction, err := wm.chain.GetWalletLock(walletPublicKeyHash) - if err != nil { - return fmt.Errorf( - "failed to get wallet lock for wallet public key hash [%s]: [%w]", - hexutils.Encode(walletPublicKeyHash[:]), - err, - ) - } - - if lockExpiration.After(time.Now()) { - logger.Infof( - "wallet [%s] is locked due to [%s] action until [%s]; skipping [%s] execution...", - hexutils.Encode(walletPublicKeyHash[:]), - lockWalletAction.String(), - lockExpiration.String(), - currentWalletAction.String(), - ) - return nil - } - - return runFunc() -} diff --git a/pkg/maintainer/wallet/wallet_test.go b/pkg/maintainer/wallet/wallet_test.go deleted file mode 100644 index b0f5166eef..0000000000 --- a/pkg/maintainer/wallet/wallet_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package wallet - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/keep-network/keep-core/internal/testutils" - "github.com/keep-network/keep-core/pkg/tbtc" -) - -func TestRunIfWalletUnlocked_WhenLocked(t *testing.T) { - localChain := NewLocalChain() - - walletPublicKeyHash := [20]byte{1} - - lockExpiration := time.Now().Add(500 * time.Millisecond) - - localChain.SetWalletLock( - walletPublicKeyHash, - lockExpiration, - tbtc.ActionHeartbeat, - ) - - runFunc := func() error { - return fmt.Errorf("boom, you should not run me") - } - - wm := &walletMaintainer{ - config: Config{}, - chain: localChain, - } - - err := wm.runIfWalletUnlocked( - context.Background(), - walletPublicKeyHash, - tbtc.ActionDepositSweep, - runFunc, - ) - if err != nil { - t.Fatal(err) - } -} - -func TestRunIfWalletUnlocked_WhenUnlocked(t *testing.T) { - tests := map[string]struct{ expectedError error }{ - "no error in runFunc": { - expectedError: nil, - }, - "propagate error from runFunc": { - expectedError: fmt.Errorf("boom, propagate up up up"), - }, - } - - for testName, test := range tests { - t.Run(testName, func(t *testing.T) { - localChain := NewLocalChain() - - walletPublicKeyHash := [20]byte{2} - - localChain.ResetWalletLock(walletPublicKeyHash) - - wasCalled := make(chan bool, 1) - runFunc := func() error { - wasCalled <- true - return test.expectedError - } - - wm := &walletMaintainer{ - config: Config{}, - chain: localChain, - } - - err := wm.runIfWalletUnlocked( - context.Background(), - walletPublicKeyHash, - tbtc.ActionDepositSweep, - runFunc, - ) - - <-wasCalled - - testutils.AssertErrorsSame(t, test.expectedError, err) - }) - } -} From 6aca781e0f6ed63a7bdc445fe1107bf8ad19048e Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 14:36:32 +0100 Subject: [PATCH 02/14] Move code from `pkg/maintainer/wallet` to `pkg/tbtcpg` package This move is meant to reflect the fact that wallet maintainer is no longer a thing and, we want to make proposal generator a standalone module that will be used by `pkg/tbtc`. By the way, we are also cleaning up proposal-related commands from the maintainer-cli. --- cmd/maintainercli.go | 357 +----------------- pkg/maintainer/wallet/wallet.go | 7 - .../wallet => tbtcpg}/bitcoin_chain_test.go | 2 +- pkg/{maintainer/wallet => tbtcpg}/chain.go | 2 +- .../wallet => tbtcpg}/chain_test.go | 2 +- .../wallet => tbtcpg}/deposit_sweep.go | 2 +- .../wallet => tbtcpg}/deposit_sweep_test.go | 18 +- .../internal/test/marshaling.go | 4 +- .../internal/test/tbtcpgtest.go} | 14 +- .../testdata/find_deposits_scenario_0.json | 0 .../testdata/find_deposits_scenario_1.json | 0 .../testdata/find_deposits_scenario_2.json | 0 .../testdata/find_deposits_scenario_3.json | 0 .../find_pending_redemptions_scenario_0.json | 0 .../find_pending_redemptions_scenario_1.json | 0 .../find_pending_redemptions_scenario_2.json | 0 .../find_pending_redemptions_scenario_3.json | 0 .../find_pending_redemptions_scenario_4.json | 0 .../find_pending_redemptions_scenario_5.json | 0 .../testdata/propose_sweep_scenario_0.json | 0 .../testdata/propose_sweep_scenario_1.json | 0 .../testdata/propose_sweep_scenario_2.json | 0 .../wallet => tbtcpg}/redemptions.go | 2 +- .../wallet => tbtcpg}/redemptions_test.go | 22 +- pkg/tbtcpg/tbtcpg.go | 7 + 25 files changed, 45 insertions(+), 394 deletions(-) delete mode 100644 pkg/maintainer/wallet/wallet.go rename pkg/{maintainer/wallet => tbtcpg}/bitcoin_chain_test.go (99%) rename pkg/{maintainer/wallet => tbtcpg}/chain.go (99%) rename pkg/{maintainer/wallet => tbtcpg}/chain_test.go (99%) rename pkg/{maintainer/wallet => tbtcpg}/deposit_sweep.go (99%) rename pkg/{maintainer/wallet => tbtcpg}/deposit_sweep_test.go (90%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/marshaling.go (99%) rename pkg/{maintainer/wallet/internal/test/wallettest.go => tbtcpg/internal/test/tbtcpgtest.go} (92%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_deposits_scenario_0.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_deposits_scenario_1.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_deposits_scenario_2.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_deposits_scenario_3.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_0.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_1.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_2.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_3.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_4.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/find_pending_redemptions_scenario_5.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/propose_sweep_scenario_0.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/propose_sweep_scenario_1.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/internal/test/testdata/propose_sweep_scenario_2.json (100%) rename pkg/{maintainer/wallet => tbtcpg}/redemptions.go (99%) rename pkg/{maintainer/wallet => tbtcpg}/redemptions_test.go (91%) create mode 100644 pkg/tbtcpg/tbtcpg.go diff --git a/cmd/maintainercli.go b/cmd/maintainercli.go index 1908869591..7d1d6f698b 100644 --- a/cmd/maintainercli.go +++ b/cmd/maintainercli.go @@ -3,9 +3,7 @@ package cmd import ( "fmt" "os" - "regexp" "sort" - "strconv" "text/tabwriter" "github.com/spf13/cobra" @@ -16,29 +14,17 @@ import ( "github.com/keep-network/keep-core/pkg/bitcoin/electrum" "github.com/keep-network/keep-core/pkg/chain/ethereum" "github.com/keep-network/keep-core/pkg/maintainer/spv" - walletmtr "github.com/keep-network/keep-core/pkg/maintainer/wallet" + "github.com/keep-network/keep-core/pkg/tbtcpg" ) var ( // listDepositsCommand: - // proposeDepositsSweepCommand: walletFlagName = "wallet" // listDepositsCommand: hideSweptFlagName = "hide-swept" headFlagName = "head" - // proposeDepositsSweepCommand: - // proposeRedemptionsCommand: - feeFlagName = "fee" - dryRunFlagName = "dry-run" - - // proposeDepositsSweepCommand: - depositSweepMaxSizeFlagName = "deposit-sweep-max-size" - - // proposeRedemptionsCommand: - redemptionMaxSizeFlagName = "redemption-max-size" - // estimateDepositsSweepFeeCommand: depositsCountFlagName = "deposits-count" @@ -117,7 +103,7 @@ var listDepositsCommand = cobra.Command{ } } - deposits, err := walletmtr.FindDeposits( + deposits, err := tbtcpg.FindDeposits( tbtcChain, btcChain, walletPublicKeyHash, @@ -144,7 +130,7 @@ var listDepositsCommand = cobra.Command{ }, } -func printDepositsTable(deposits []*walletmtr.Deposit) error { +func printDepositsTable(deposits []*tbtcpg.Deposit) error { w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, ' ', tabwriter.AlignRight) fmt.Fprintf(w, "index\twallet\ttype\tvalue (BTC)\tdeposit key\trevealed deposit data\tconfirmations\tswept\t\n") @@ -173,287 +159,6 @@ func printDepositsTable(deposits []*walletmtr.Deposit) error { return nil } -var proposeDepositsSweepCommand = cobra.Command{ - Use: "propose-deposits-sweep", - Short: "propose deposits sweep", - Long: proposeDepositsSweepCommandDescription, - TraverseChildren: true, - Args: func(cmd *cobra.Command, args []string) error { - for i, arg := range args { - if err := validateDepositReferenceString(arg); err != nil { - return fmt.Errorf( - "argument [%d] failed validation: %v", - i, - err, - ) - } - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - wallet, err := cmd.Flags().GetString(walletFlagName) - if err != nil { - return fmt.Errorf("failed to find wallet flag: %v", err) - } - - fee, err := cmd.Flags().GetInt64(feeFlagName) - if err != nil { - return fmt.Errorf("failed to find fee flag: %v", err) - } - - depositSweepMaxSize, err := cmd.Flags().GetUint16(depositSweepMaxSizeFlagName) - if err != nil { - return fmt.Errorf("failed to find deposit sweep max size flag: %v", err) - } - - dryRun, err := cmd.Flags().GetBool(dryRunFlagName) - if err != nil { - return fmt.Errorf("failed to find dry run flag: %v", err) - } - - _, tbtcChain, _, _, _, err := ethereum.Connect(cmd.Context(), clientConfig.Ethereum) - if err != nil { - return fmt.Errorf( - "could not connect to Ethereum chain: [%v]", - err, - ) - } - - btcChain, err := electrum.Connect(ctx, clientConfig.Bitcoin.Electrum) - if err != nil { - return fmt.Errorf("could not connect to Electrum chain: [%v]", err) - } - - var walletPublicKeyHash [20]byte - if len(wallet) > 0 { - var err error - walletPublicKeyHash, err = newWalletPublicKeyHash(wallet) - if err != nil { - return fmt.Errorf("failed extract wallet public key hash: %v", err) - } - } - - if depositSweepMaxSize == 0 { - depositSweepMaxSize, err = tbtcChain.GetDepositSweepMaxSize() - if err != nil { - return fmt.Errorf("failed to get deposit sweep max size: [%v]", err) - } - } - - var deposits []*walletmtr.DepositReference - if len(args) > 0 { - deposits, err = parseDepositsReferences(args) - if err != nil { - return fmt.Errorf("failed extract wallet public key hash: %v", err) - } - } else { - walletPublicKeyHash, deposits, err = walletmtr.FindDepositsToSweep( - tbtcChain, - btcChain, - walletPublicKeyHash, - depositSweepMaxSize, - ) - if err != nil { - return fmt.Errorf("failed to prepare deposits sweep proposal: %v", err) - } - } - - if len(deposits) > int(depositSweepMaxSize) { - return fmt.Errorf( - "deposits number [%d] is greater than deposit sweep max size [%d]", - len(deposits), - depositSweepMaxSize, - ) - } - - return walletmtr.ProposeDepositsSweep( - tbtcChain, - btcChain, - walletPublicKeyHash, - fee, - deposits, - dryRun, - ) - }, -} - -var ( - depositsFormatDescription = "Deposits details should be provided as strings containing: \n" + - " - bitcoin transaction hash (unprefixed bitcoin transaction hash in reverse (RPC) order), \n" + - " - bitcoin transaction output index, \n" + - " - ethereum block number when the deposit was revealed to the chain. \n" + - "The properties should be separated by semicolons, in the following format: \n" + - depositReferenceFormatPattern + "\n" + - "e.g. bd99d1d0a61fd104925d9b7ac997958aa8af570418b3fde091f7bfc561608865:1:8392394" - - depositReferenceFormatPattern = ":" + - ":" + - "" - - depositReferenceFormatRegexp = regexp.MustCompile(`^([[:xdigit:]]+):(\d+):(\d+)$`) - - proposeDepositsSweepCommandDescription = "Submits a deposits sweep proposal " + - "to the chain. Expects --wallet and --fee flags along with deposits to " + - "sweep provided as arguments.\n" + - depositsFormatDescription -) - -// parseDepositsReferences decodes a list of deposits references. -func parseDepositsReferences( - depositsRefsStings []string, -) ([]*walletmtr.DepositReference, error) { - depositsRefs := make([]*walletmtr.DepositReference, len(depositsRefsStings)) - - for i, depositRefString := range depositsRefsStings { - matched := depositReferenceFormatRegexp.FindStringSubmatch(depositRefString) - // Check if number of resolved entries match expected number of groups - // for the given regexp. - if len(matched) != 4 { - return nil, fmt.Errorf( - "failed to parse deposit: [%s]", - depositRefString, - ) - } - - txHash, err := bitcoin.NewHashFromString(matched[1], bitcoin.ReversedByteOrder) - if err != nil { - return nil, fmt.Errorf( - "invalid bitcoin transaction hash [%s]: %v", - matched[1], - err, - ) - } - - outputIndex, err := strconv.ParseInt(matched[2], 10, 32) - if err != nil { - return nil, fmt.Errorf( - "invalid bitcoin transaction output index [%s]: %v", - matched[2], - err, - ) - } - - revealBlock, err := strconv.ParseUint(matched[3], 10, 32) - if err != nil { - return nil, fmt.Errorf( - "invalid reveal block number [%s]: %v", - matched[3], - err, - ) - } - - depositsRefs[i] = &walletmtr.DepositReference{ - FundingTxHash: txHash, - FundingOutputIndex: uint32(outputIndex), - RevealBlock: revealBlock, - } - } - - return depositsRefs, nil -} - -// validateDepositReferenceString validates format of the string containing a -// deposit reference. -func validateDepositReferenceString(depositRefString string) error { - if !depositReferenceFormatRegexp.MatchString(depositRefString) { - return fmt.Errorf( - "[%s] doesn't match pattern: %s", - depositRefString, - depositReferenceFormatPattern, - ) - } - return nil -} - -var proposeRedemptionCommand = cobra.Command{ - Use: "propose-redemption", - Short: "propose redemption", - Long: "Submits a redemption proposal to the chain.", - TraverseChildren: true, - RunE: func(cmd *cobra.Command, args []string) error { - wallet, err := cmd.Flags().GetString(walletFlagName) - if err != nil { - return fmt.Errorf("failed to find wallet flag: %v", err) - } - - fee, err := cmd.Flags().GetInt64(feeFlagName) - if err != nil { - return fmt.Errorf("failed to find fee flag: %v", err) - } - - redemptionMaxSize, err := cmd.Flags().GetUint16(redemptionMaxSizeFlagName) - if err != nil { - return fmt.Errorf("failed to find redemption max size flag: %v", err) - } - - dryRun, err := cmd.Flags().GetBool(dryRunFlagName) - if err != nil { - return fmt.Errorf("failed to find dry run flag: %v", err) - } - - _, tbtcChain, _, _, _, err := ethereum.Connect(cmd.Context(), clientConfig.Ethereum) - if err != nil { - return fmt.Errorf( - "could not connect to Ethereum chain: [%v]", - err, - ) - } - - btcChain, err := electrum.Connect(cmd.Context(), clientConfig.Bitcoin.Electrum) - if err != nil { - return fmt.Errorf("could not connect to Electrum chain: [%v]", err) - } - - var walletPublicKeyHashes [][20]byte - if len(wallet) > 0 { - walletPublicKeyHash, err := newWalletPublicKeyHash(wallet) - if err != nil { - return fmt.Errorf("failed extract wallet public key hash: %v", err) - } - - walletPublicKeyHashes = append(walletPublicKeyHashes, walletPublicKeyHash) - } - - if redemptionMaxSize == 0 { - redemptionMaxSize, err = tbtcChain.GetRedemptionMaxSize() - if err != nil { - return fmt.Errorf("failed to get redemption max size: [%v]", err) - } - } - - walletsPendingRedemptions, err := walletmtr.FindPendingRedemptions( - tbtcChain, - walletmtr.PendingRedemptionsFilter{ - WalletPublicKeyHashes: walletPublicKeyHashes, - WalletsLimit: 1, - RequestsLimit: redemptionMaxSize, - RequestAmountLimit: 0, - }, - ) - if err != nil { - return fmt.Errorf("failed to find pending redemption requests: [%w]", err) - } - - for walletPublicKeyHash, redeemersOutputScripts := range walletsPendingRedemptions { - err := walletmtr.ProposeRedemption( - tbtcChain, - btcChain, - walletPublicKeyHash, - fee, - redeemersOutputScripts, - dryRun, - ) - if err != nil { - return err - } - } - - return nil - }, -} - var estimateDepositsSweepFeeCommand = cobra.Command{ Use: "estimate-deposits-sweep-fee", Short: "estimates deposits sweep fee", @@ -480,7 +185,7 @@ var estimateDepositsSweepFeeCommand = cobra.Command{ return fmt.Errorf("could not connect to Electrum chain: [%v]", err) } - fees, err := walletmtr.EstimateDepositsSweepFee( + fees, err := tbtcpg.EstimateDepositsSweepFee( tbtcChain, btcChain, depositsCount, @@ -780,60 +485,6 @@ func init() { MaintainerCliCommand.AddCommand(&listDepositsCommand) - // Propose Deposits Sweep Subcommand - proposeDepositsSweepCommand.Flags().String( - walletFlagName, - "", - "wallet public key hash", - ) - - proposeDepositsSweepCommand.Flags().Int64( - feeFlagName, - 0, - "fee for the entire bitcoin transaction (satoshi)", - ) - - proposeDepositsSweepCommand.Flags().Uint16( - depositSweepMaxSizeFlagName, - 0, - "maximum count of deposits that can be swept within a single sweep", - ) - - proposeDepositsSweepCommand.Flags().Bool( - dryRunFlagName, - false, - "don't submit a proposal to the chain", - ) - - MaintainerCliCommand.AddCommand(&proposeDepositsSweepCommand) - - // Propose Redemptions Subcommand - proposeRedemptionCommand.Flags().String( - walletFlagName, - "", - "wallet public key hash", - ) - - proposeRedemptionCommand.Flags().Int64( - feeFlagName, - 0, - "fee for the entire bitcoin transaction (satoshi)", - ) - - proposeRedemptionCommand.Flags().Uint16( - redemptionMaxSizeFlagName, - 0, - "maximum count of deposits that can be redeemed within a single redemption", - ) - - proposeRedemptionCommand.Flags().Bool( - dryRunFlagName, - false, - "don't submit a proposal to the chain", - ) - - MaintainerCliCommand.AddCommand(&proposeRedemptionCommand) - // Estimate Deposits Sweep Fee Subcommand. estimateDepositsSweepFeeCommand.Flags().Int( depositsCountFlagName, diff --git a/pkg/maintainer/wallet/wallet.go b/pkg/maintainer/wallet/wallet.go deleted file mode 100644 index 0535528ea0..0000000000 --- a/pkg/maintainer/wallet/wallet.go +++ /dev/null @@ -1,7 +0,0 @@ -package wallet - -import ( - "github.com/ipfs/go-log/v2" -) - -var logger = log.Logger("keep-maintainer-wallet") diff --git a/pkg/maintainer/wallet/bitcoin_chain_test.go b/pkg/tbtcpg/bitcoin_chain_test.go similarity index 99% rename from pkg/maintainer/wallet/bitcoin_chain_test.go rename to pkg/tbtcpg/bitcoin_chain_test.go index eb608482f2..8939fdea20 100644 --- a/pkg/maintainer/wallet/bitcoin_chain_test.go +++ b/pkg/tbtcpg/bitcoin_chain_test.go @@ -1,4 +1,4 @@ -package wallet +package tbtcpg import ( "fmt" diff --git a/pkg/maintainer/wallet/chain.go b/pkg/tbtcpg/chain.go similarity index 99% rename from pkg/maintainer/wallet/chain.go rename to pkg/tbtcpg/chain.go index 3827c7eae6..310e3ad0db 100644 --- a/pkg/maintainer/wallet/chain.go +++ b/pkg/tbtcpg/chain.go @@ -1,4 +1,4 @@ -package wallet +package tbtcpg import ( "github.com/keep-network/keep-core/pkg/bitcoin" diff --git a/pkg/maintainer/wallet/chain_test.go b/pkg/tbtcpg/chain_test.go similarity index 99% rename from pkg/maintainer/wallet/chain_test.go rename to pkg/tbtcpg/chain_test.go index 5149c94368..3d5b938089 100644 --- a/pkg/maintainer/wallet/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -1,4 +1,4 @@ -package wallet +package tbtcpg import ( "bytes" diff --git a/pkg/maintainer/wallet/deposit_sweep.go b/pkg/tbtcpg/deposit_sweep.go similarity index 99% rename from pkg/maintainer/wallet/deposit_sweep.go rename to pkg/tbtcpg/deposit_sweep.go index 4c421d7b85..4869157d85 100644 --- a/pkg/maintainer/wallet/deposit_sweep.go +++ b/pkg/tbtcpg/deposit_sweep.go @@ -1,4 +1,4 @@ -package wallet +package tbtcpg import ( "fmt" diff --git a/pkg/maintainer/wallet/deposit_sweep_test.go b/pkg/tbtcpg/deposit_sweep_test.go similarity index 90% rename from pkg/maintainer/wallet/deposit_sweep_test.go rename to pkg/tbtcpg/deposit_sweep_test.go index 6d32dcb731..843beb385c 100644 --- a/pkg/maintainer/wallet/deposit_sweep_test.go +++ b/pkg/tbtcpg/deposit_sweep_test.go @@ -1,4 +1,4 @@ -package wallet_test +package tbtcpg_test import ( "reflect" @@ -8,9 +8,9 @@ import ( "github.com/ipfs/go-log" "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" - walletmtr "github.com/keep-network/keep-core/pkg/maintainer/wallet" - "github.com/keep-network/keep-core/pkg/maintainer/wallet/internal/test" "github.com/keep-network/keep-core/pkg/tbtc" + "github.com/keep-network/keep-core/pkg/tbtcpg" + "github.com/keep-network/keep-core/pkg/tbtcpg/internal/test" ) func TestFindDepositsToSweep(t *testing.T) { @@ -26,8 +26,8 @@ func TestFindDepositsToSweep(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Title, func(t *testing.T) { - tbtcChain := walletmtr.NewLocalChain() - btcChain := walletmtr.NewLocalBitcoinChain() + tbtcChain := tbtcpg.NewLocalChain() + btcChain := tbtcpg.NewLocalBitcoinChain() expectedWallet := scenario.ExpectedWalletPublicKeyHash @@ -72,7 +72,7 @@ func TestFindDepositsToSweep(t *testing.T) { } // Test execution. - actualWallet, actualDeposits, err := walletmtr.FindDepositsToSweep( + actualWallet, actualDeposits, err := tbtcpg.FindDepositsToSweep( tbtcChain, btcChain, scenario.WalletPublicKeyHash, @@ -111,8 +111,8 @@ func TestProposeDepositsSweep(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Title, func(t *testing.T) { - tbtcChain := walletmtr.NewLocalChain() - btcChain := walletmtr.NewLocalBitcoinChain() + tbtcChain := tbtcpg.NewLocalChain() + btcChain := tbtcpg.NewLocalBitcoinChain() // Chain setup. tbtcChain.SetDepositParameters(0, 0, scenario.DepositTxMaxFee, 0) @@ -152,7 +152,7 @@ func TestProposeDepositsSweep(t *testing.T) { btcChain.SetEstimateSatPerVByteFee(1, scenario.EstimateSatPerVByteFee) // Test execution. - err = walletmtr.ProposeDepositsSweep( + err = tbtcpg.ProposeDepositsSweep( tbtcChain, btcChain, scenario.WalletPublicKeyHash, diff --git a/pkg/maintainer/wallet/internal/test/marshaling.go b/pkg/tbtcpg/internal/test/marshaling.go similarity index 99% rename from pkg/maintainer/wallet/internal/test/marshaling.go rename to pkg/tbtcpg/internal/test/marshaling.go index 46e61df931..70f77d20cd 100644 --- a/pkg/maintainer/wallet/internal/test/marshaling.go +++ b/pkg/tbtcpg/internal/test/marshaling.go @@ -7,7 +7,7 @@ import ( "math/big" "time" - walletmtr "github.com/keep-network/keep-core/pkg/maintainer/wallet" + "github.com/keep-network/keep-core/pkg/tbtcpg" "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" @@ -120,7 +120,7 @@ func (dsts *FindDepositsToSweepTestScenario) UnmarshalJSON(data []byte) error { // Unmarshal expected unswept deposits. for i, deposit := range unmarshaled.ExpectedUnsweptDeposits { - ud := new(walletmtr.DepositReference) + ud := new(tbtcpg.DepositReference) fundingTxHash, err := bitcoin.NewHashFromString(deposit.FundingTxHash, bitcoin.ReversedByteOrder) if err != nil { diff --git a/pkg/maintainer/wallet/internal/test/wallettest.go b/pkg/tbtcpg/internal/test/tbtcpgtest.go similarity index 92% rename from pkg/maintainer/wallet/internal/test/wallettest.go rename to pkg/tbtcpg/internal/test/tbtcpgtest.go index 8d3a478d3a..b27dde21dd 100644 --- a/pkg/maintainer/wallet/internal/test/wallettest.go +++ b/pkg/tbtcpg/internal/test/tbtcpgtest.go @@ -10,7 +10,7 @@ import ( "strings" "time" - walletmtr "github.com/keep-network/keep-core/pkg/maintainer/wallet" + "github.com/keep-network/keep-core/pkg/tbtcpg" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" @@ -53,7 +53,7 @@ type FindDepositsToSweepTestScenario struct { Deposits []*Deposit ExpectedWalletPublicKeyHash [20]byte - ExpectedUnsweptDeposits []*walletmtr.DepositReference + ExpectedUnsweptDeposits []*tbtcpg.DepositReference SweepTxFee int64 EstimateSatPerVByteFee int64 @@ -65,7 +65,7 @@ func LoadFindDepositsToSweepTestScenario() ([]*FindDepositsToSweepTestScenario, } type ProposeSweepDepositsData struct { - walletmtr.DepositReference + tbtcpg.DepositReference Transaction *bitcoin.Transaction FundingTxConfirmations uint @@ -84,10 +84,10 @@ type ProposeSweepTestScenario struct { ExpectedErr error } -func (psts *ProposeSweepTestScenario) DepositsReferences() []*walletmtr.DepositReference { - result := make([]*walletmtr.DepositReference, len(psts.Deposits)) +func (psts *ProposeSweepTestScenario) DepositsReferences() []*tbtcpg.DepositReference { + result := make([]*tbtcpg.DepositReference, len(psts.Deposits)) for i, d := range psts.Deposits { - result[i] = &walletmtr.DepositReference{ + result[i] = &tbtcpg.DepositReference{ FundingTxHash: d.FundingTxHash, FundingOutputIndex: d.FundingOutputIndex, RevealBlock: d.RevealBlock, @@ -121,7 +121,7 @@ type FindPendingRedemptionsTestScenario struct { RequestTimeout uint32 RequestMinAge uint32 } - Filter walletmtr.PendingRedemptionsFilter + Filter tbtcpg.PendingRedemptionsFilter Wallets []*Wallet PendingRedemptions []*RedemptionRequest ExpectedWalletsPendingRedemptions map[[20]byte][]bitcoin.Script diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_0.json rename to pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_1.json rename to pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_2.json rename to pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_3.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_deposits_scenario_3.json rename to pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_0.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_1.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_2.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_3.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_3.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_4.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_4.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_5.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/find_pending_redemptions_scenario_5.json rename to pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_0.json rename to pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_1.json rename to pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json diff --git a/pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json similarity index 100% rename from pkg/maintainer/wallet/internal/test/testdata/propose_sweep_scenario_2.json rename to pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json diff --git a/pkg/maintainer/wallet/redemptions.go b/pkg/tbtcpg/redemptions.go similarity index 99% rename from pkg/maintainer/wallet/redemptions.go rename to pkg/tbtcpg/redemptions.go index 1096b5f553..af40695d7e 100644 --- a/pkg/maintainer/wallet/redemptions.go +++ b/pkg/tbtcpg/redemptions.go @@ -1,4 +1,4 @@ -package wallet +package tbtcpg import ( "encoding/hex" diff --git a/pkg/maintainer/wallet/redemptions_test.go b/pkg/tbtcpg/redemptions_test.go similarity index 91% rename from pkg/maintainer/wallet/redemptions_test.go rename to pkg/tbtcpg/redemptions_test.go index e29341b574..781b4722a9 100644 --- a/pkg/maintainer/wallet/redemptions_test.go +++ b/pkg/tbtcpg/redemptions_test.go @@ -1,13 +1,13 @@ -package wallet_test +package tbtcpg_test import ( "encoding/hex" "github.com/go-test/deep" "github.com/keep-network/keep-core/internal/testutils" "github.com/keep-network/keep-core/pkg/bitcoin" - walletmtr "github.com/keep-network/keep-core/pkg/maintainer/wallet" - "github.com/keep-network/keep-core/pkg/maintainer/wallet/internal/test" "github.com/keep-network/keep-core/pkg/tbtc" + "github.com/keep-network/keep-core/pkg/tbtcpg" + "github.com/keep-network/keep-core/pkg/tbtcpg/internal/test" "math/big" "testing" ) @@ -23,7 +23,7 @@ func TestEstimateRedemptionFee(t *testing.T) { return bytes } - btcChain := walletmtr.NewLocalBitcoinChain() + btcChain := tbtcpg.NewLocalBitcoinChain() btcChain.SetEstimateSatPerVByteFee(1, 16) redeemersOutputScripts := []bitcoin.Script{ @@ -33,7 +33,7 @@ func TestEstimateRedemptionFee(t *testing.T) { fromHex("0020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c"), // P2WSH } - actualFee, err := walletmtr.EstimateRedemptionFee(btcChain, redeemersOutputScripts) + actualFee, err := tbtcpg.EstimateRedemptionFee(btcChain, redeemersOutputScripts) if err != nil { t.Fatal(err) } @@ -50,7 +50,7 @@ func TestFindPendingRedemptions(t *testing.T) { for _, scenario := range scenarios { t.Run(scenario.Title, func(t *testing.T) { - tbtcChain := walletmtr.NewLocalChain() + tbtcChain := tbtcpg.NewLocalChain() // Set the average block time enforced by the scenario. tbtcChain.SetAverageBlockTime(scenario.ChainParameters.AverageBlockTime) @@ -58,7 +58,7 @@ func TestFindPendingRedemptions(t *testing.T) { // Set the scenario's current block using a mock block counter. // This is needed to build a proper filter for the // `PastRedemptionRequestedEvents` call. - blockCounter := walletmtr.NewMockBlockCounter() + blockCounter := tbtcpg.NewMockBlockCounter() blockCounter.SetCurrentBlock(scenario.ChainParameters.CurrentBlock) tbtcChain.SetBlockCounter(blockCounter) @@ -121,7 +121,7 @@ func TestFindPendingRedemptions(t *testing.T) { ) } - walletsPendingRedemptions, err := walletmtr.FindPendingRedemptions( + walletsPendingRedemptions, err := tbtcpg.FindPendingRedemptions( tbtcChain, scenario.Filter, ) @@ -180,8 +180,8 @@ func TestProposeRedemption(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { - tbtcChain := walletmtr.NewLocalChain() - btcChain := walletmtr.NewLocalBitcoinChain() + tbtcChain := tbtcpg.NewLocalChain() + btcChain := tbtcpg.NewLocalBitcoinChain() btcChain.SetEstimateSatPerVByteFee(1, 25) @@ -202,7 +202,7 @@ func TestProposeRedemption(t *testing.T) { t.Fatal(err) } - err = walletmtr.ProposeRedemption( + err = tbtcpg.ProposeRedemption( tbtcChain, btcChain, walletPublicKeyHash, diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go new file mode 100644 index 0000000000..023a9db37d --- /dev/null +++ b/pkg/tbtcpg/tbtcpg.go @@ -0,0 +1,7 @@ +package tbtcpg + +import ( + "github.com/ipfs/go-log/v2" +) + +var logger = log.Logger("keep-tbtcpg") From 4a399f955dca6b1b8719e5bdc84075e6bf9f7994 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 14:51:42 +0100 Subject: [PATCH 03/14] Prepare `pkg/tbtc` for integration with `pkg/tbtcpg` --- pkg/tbtc/coordination.go | 74 +++++++++++++------------ pkg/tbtc/coordination_test.go | 101 ++++++++++++++++++++++------------ pkg/tbtc/deposit_sweep.go | 4 +- pkg/tbtc/heartbeat.go | 4 +- pkg/tbtc/marshaling.go | 12 ++-- pkg/tbtc/marshaling_test.go | 6 +- pkg/tbtc/node.go | 12 +--- pkg/tbtc/node_test.go | 10 ++-- pkg/tbtc/redemption.go | 4 +- 9 files changed, 127 insertions(+), 100 deletions(-) diff --git a/pkg/tbtc/coordination.go b/pkg/tbtc/coordination.go index b7d1259e7e..aa52c9942c 100644 --- a/pkg/tbtc/coordination.go +++ b/pkg/tbtc/coordination.go @@ -195,39 +195,42 @@ func (cf *coordinationFault) String() string { ) } -// coordinationProposalGenerator is a function that generates a coordination -// proposal based on the given checklist of possible wallet actions. -// The checklist is a list of actions that should be checked for the given -// coordination window. The generator is expected to return a proposal -// for the first action from the checklist that is valid for the given -// wallet's state. If none of the actions are valid, the generator -// should return a noopProposal. -type coordinationProposalGenerator func( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, -) (coordinationProposal, error) +// CoordinationProposalGenerator is a component responsible for generating +// coordination proposals. +type CoordinationProposalGenerator interface { + // Generate generates a coordination proposal based on the given checklist + // of possible wallet actions. The checklist is a list of actions that + // should be checked for the given coordination window. The generator is + // expected to return a proposal for the first action from the checklist + // that is valid for the given wallet's state. If none of the actions are + // valid, the generator should return a noopProposal. + Generate( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, + ) (CoordinationProposal, error) +} -// coordinationProposal represents a single action proposal for the given wallet. -type coordinationProposal interface { +// CoordinationProposal represents a single action proposal for the given wallet. +type CoordinationProposal interface { pb.Marshaler pb.Unmarshaler - // actionType returns the specific type of the walletAction being subject + // ActionType returns the specific type of the walletAction being subject // of this proposal. - actionType() WalletActionType - // validityBlocks returns the number of blocks for which the proposal is + ActionType() WalletActionType + // ValidityBlocks returns the number of blocks for which the proposal is // valid. This value SHOULD NOT be marshaled/unmarshaled. - validityBlocks() uint64 + ValidityBlocks() uint64 } -// noopProposal is a proposal that does not propose any action. -type noopProposal struct{} +// NoopProposal is a proposal that does not propose any action. +type NoopProposal struct{} -func (np *noopProposal) actionType() WalletActionType { +func (np *NoopProposal) ActionType() WalletActionType { return ActionNoop } -func (np *noopProposal) validityBlocks() uint64 { +func (np *NoopProposal) ValidityBlocks() uint64 { // Panic to make sure that the proposal is not processed by the node. panic("noop proposal does not have validity blocks") } @@ -238,7 +241,7 @@ type coordinationResult struct { wallet wallet window *coordinationWindow leader chain.Address - proposal coordinationProposal + proposal CoordinationProposal faults []*coordinationFault } @@ -248,7 +251,7 @@ func (cr *coordinationResult) String() string { &cr.wallet, cr.window.coordinationBlock, cr.leader, - cr.proposal.actionType(), + cr.proposal.ActionType(), cr.faults, ) } @@ -259,7 +262,7 @@ type coordinationMessage struct { senderID group.MemberIndex coordinationBlock uint64 walletPublicKeyHash [20]byte - proposal coordinationProposal + proposal CoordinationProposal } func (cm *coordinationMessage) Type() string { @@ -277,7 +280,7 @@ type coordinationExecutor struct { membersIndexes []group.MemberIndex operatorAddress chain.Address - proposalGenerator coordinationProposalGenerator + proposalGenerator CoordinationProposalGenerator broadcastChannel net.BroadcastChannel membershipValidator *group.MembershipValidator @@ -293,7 +296,7 @@ func newCoordinationExecutor( coordinatedWallet wallet, membersIndexes []group.MemberIndex, operatorAddress chain.Address, - proposalGenerator coordinationProposalGenerator, + proposalGenerator CoordinationProposalGenerator, broadcastChannel net.BroadcastChannel, membershipValidator *group.MembershipValidator, protocolLatch *generator.ProtocolLatch, @@ -376,7 +379,7 @@ func (ce *coordinationExecutor) coordinate( ) defer cancelCtx() - var proposal coordinationProposal + var proposal CoordinationProposal var faults []*coordinationFault if leader == ce.operatorAddress { @@ -394,7 +397,7 @@ func (ce *coordinationExecutor) coordinate( ) } - execLogger.Info("broadcasted proposal: [%s]", proposal.actionType()) + execLogger.Info("broadcasted proposal: [%s]", proposal.ActionType()) } else { execLogger.Info("executing follower's routine") @@ -413,14 +416,14 @@ func (ce *coordinationExecutor) coordinate( execLogger.Info( "received proposal: [%s]; observed faults: [%v]", - proposal.actionType(), + proposal.ActionType(), faults, ) } // Just in case, if the proposal is nil, set it to noop. if proposal == nil { - proposal = &noopProposal{} + proposal = &NoopProposal{} } result := &coordinationResult{ @@ -551,10 +554,13 @@ func (ce *coordinationExecutor) executeLeaderRoutine( ctx context.Context, coordinationBlock uint64, actionsChecklist []WalletActionType, -) (coordinationProposal, error) { +) (CoordinationProposal, error) { walletPublicKeyHash := ce.walletPublicKeyHash() - proposal, err := ce.proposalGenerator(walletPublicKeyHash, actionsChecklist) + proposal, err := ce.proposalGenerator.Generate( + walletPublicKeyHash, + actionsChecklist, + ) if err != nil { return nil, fmt.Errorf("failed to generate proposal: [%v]", err) } @@ -593,7 +599,7 @@ func (ce *coordinationExecutor) executeFollowerRoutine( leader chain.Address, coordinationBlock uint64, actionsAllowed []WalletActionType, -) (coordinationProposal, []*coordinationFault, error) { +) (CoordinationProposal, []*coordinationFault, error) { // Cache wallet public key hash to not compute it on every message. walletPublicKeyHash := ce.walletPublicKeyHash() // Leader ID is the index of the first (index-wise) member controlled by @@ -661,7 +667,7 @@ loop: // Filter out messages that propose an action that is not allowed // for the given coordination window. - if !slices.Contains(actionsAllowed, message.proposal.actionType()) { + if !slices.Contains(actionsAllowed, message.proposal.ActionType()) { faults = append( faults, &coordinationFault{ culprit: leader, diff --git a/pkg/tbtc/coordination_test.go b/pkg/tbtc/coordination_test.go index 7ccd0481a9..d8a6ea53b3 100644 --- a/pkg/tbtc/coordination_test.go +++ b/pkg/tbtc/coordination_test.go @@ -310,24 +310,26 @@ func TestCoordinationExecutor_Coordinate(t *testing.T) { }, } - proposalGenerator := func( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, - ) (coordinationProposal, error) { - for _, action := range actionsChecklist { - if walletPublicKeyHash == publicKeyHash && action == ActionRedemption { - return &RedemptionProposal{ - RedeemersOutputScripts: []bitcoin.Script{ - parseScript("00148db50eb52063ea9d98b3eac91489a90f738986f6"), - parseScript("76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac"), - }, - RedemptionTxFee: big.NewInt(10000), - }, nil + proposalGenerator := newMockCoordinationProposalGenerator( + func( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, + ) (CoordinationProposal, error) { + for _, action := range actionsChecklist { + if walletPublicKeyHash == publicKeyHash && action == ActionRedemption { + return &RedemptionProposal{ + RedeemersOutputScripts: []bitcoin.Script{ + parseScript("00148db50eb52063ea9d98b3eac91489a90f738986f6"), + parseScript("76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac"), + }, + RedemptionTxFee: big.NewInt(10000), + }, nil + } } - } - return &noopProposal{}, nil - } + return &NoopProposal{}, nil + }, + ) membershipValidator := group.NewMembershipValidator( &testutils.MockLogger{}, @@ -690,23 +692,25 @@ func TestCoordinationExecutor_ExecuteLeaderRoutine(t *testing.T) { // sender. membersIndexes := []group.MemberIndex{77, 5, 10} - proposalGenerator := func( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, - ) ( - coordinationProposal, - error, - ) { - for _, action := range actionsChecklist { - if walletPublicKeyHash == publicKeyHash && action == ActionHeartbeat { - return &HeartbeatProposal{ - Message: []byte("heartbeat message"), - }, nil + proposalGenerator := newMockCoordinationProposalGenerator( + func( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, + ) ( + CoordinationProposal, + error, + ) { + for _, action := range actionsChecklist { + if walletPublicKeyHash == publicKeyHash && action == ActionHeartbeat { + return &HeartbeatProposal{ + Message: []byte("heartbeat message"), + }, nil + } } - } - return &noopProposal{}, nil - } + return &NoopProposal{}, nil + }, + ) provider := netlocal.Connect() @@ -922,7 +926,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { senderID: coordinatedWallet.membersByOperator(follower1.address)[0], coordinationBlock: 900, walletPublicKeyHash: executor.walletPublicKeyHash(), - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }) if err != nil { t.Error(err) @@ -935,7 +939,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { senderID: coordinatedWallet.membersByOperator(follower2.address)[0], coordinationBlock: 900, walletPublicKeyHash: executor.walletPublicKeyHash(), - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }) if err != nil { t.Error(err) @@ -948,7 +952,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { senderID: leaderID, coordinationBlock: 901, walletPublicKeyHash: executor.walletPublicKeyHash(), - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }) if err != nil { t.Error(err) @@ -960,7 +964,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { senderID: leaderID, coordinationBlock: 900, walletPublicKeyHash: [20]byte{0x01}, - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }) if err != nil { t.Error(err) @@ -972,7 +976,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { senderID: coordinatedWallet.membersByOperator(follower2.address)[0], coordinationBlock: 900, walletPublicKeyHash: executor.walletPublicKeyHash(), - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }) if err != nil { t.Error(err) @@ -1161,3 +1165,28 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine_WithIdleLeader(t *testing.T ) } } + +type mockCoordinationProposalGenerator struct { + delegate func( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, + ) (CoordinationProposal, error) +} + +func newMockCoordinationProposalGenerator( + delegate func( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, + ) (CoordinationProposal, error), +) *mockCoordinationProposalGenerator { + return &mockCoordinationProposalGenerator{ + delegate: delegate, + } +} + +func (mcpg *mockCoordinationProposalGenerator) Generate( + walletPublicKeyHash [20]byte, + actionsChecklist []WalletActionType, +) (CoordinationProposal, error) { + return mcpg.delegate(walletPublicKeyHash, actionsChecklist) +} diff --git a/pkg/tbtc/deposit_sweep.go b/pkg/tbtc/deposit_sweep.go index 49df18b3bc..63c66ca21d 100644 --- a/pkg/tbtc/deposit_sweep.go +++ b/pkg/tbtc/deposit_sweep.go @@ -67,11 +67,11 @@ type DepositSweepProposal struct { DepositsRevealBlocks []*big.Int } -func (dsp *DepositSweepProposal) actionType() WalletActionType { +func (dsp *DepositSweepProposal) ActionType() WalletActionType { return ActionDepositSweep } -func (dsp *DepositSweepProposal) validityBlocks() uint64 { +func (dsp *DepositSweepProposal) ValidityBlocks() uint64 { return depositSweepProposalValidityBlocks } diff --git a/pkg/tbtc/heartbeat.go b/pkg/tbtc/heartbeat.go index 57714f899a..b4945d91d4 100644 --- a/pkg/tbtc/heartbeat.go +++ b/pkg/tbtc/heartbeat.go @@ -34,11 +34,11 @@ type HeartbeatProposal struct { Message []byte } -func (hp *HeartbeatProposal) actionType() WalletActionType { +func (hp *HeartbeatProposal) ActionType() WalletActionType { return ActionHeartbeat } -func (hp *HeartbeatProposal) validityBlocks() uint64 { +func (hp *HeartbeatProposal) ValidityBlocks() uint64 { return heartbeatProposalValidityBlocks } diff --git a/pkg/tbtc/marshaling.go b/pkg/tbtc/marshaling.go index 470736192c..88dacb5183 100644 --- a/pkg/tbtc/marshaling.go +++ b/pkg/tbtc/marshaling.go @@ -135,7 +135,7 @@ func (cm *coordinationMessage) Marshal() ([]byte, error) { } pbProposal := &pb.CoordinationProposal{ - ActionType: uint32(cm.proposal.actionType()), + ActionType: uint32(cm.proposal.ActionType()), Payload: proposalBytes, } @@ -206,7 +206,7 @@ func unmarshalWalletPublicKeyHash(bytes []byte) ([20]byte, error) { // unmarshalCoordinationProposal converts a byte array back to the coordination // proposal. func unmarshalCoordinationProposal(actionType uint32, payload []byte) ( - coordinationProposal, + CoordinationProposal, error, ) { if actionType > math.MaxUint8 { @@ -224,8 +224,8 @@ func unmarshalCoordinationProposal(actionType uint32, payload []byte) ( ) } - proposal, ok := map[WalletActionType]coordinationProposal{ - ActionNoop: &noopProposal{}, + proposal, ok := map[WalletActionType]CoordinationProposal{ + ActionNoop: &NoopProposal{}, ActionHeartbeat: &HeartbeatProposal{}, ActionDepositSweep: &DepositSweepProposal{}, ActionRedemption: &RedemptionProposal{}, @@ -248,12 +248,12 @@ func unmarshalCoordinationProposal(actionType uint32, payload []byte) ( } // Marshal converts the noopProposal to a byte array. -func (np *noopProposal) Marshal() ([]byte, error) { +func (np *NoopProposal) Marshal() ([]byte, error) { return []byte{}, nil } // Unmarshal converts a byte array back to the noopProposal. -func (np *noopProposal) Unmarshal([]byte) error { +func (np *NoopProposal) Unmarshal([]byte) error { return nil } diff --git a/pkg/tbtc/marshaling_test.go b/pkg/tbtc/marshaling_test.go index e1e11abfad..a2fbb73996 100644 --- a/pkg/tbtc/marshaling_test.go +++ b/pkg/tbtc/marshaling_test.go @@ -127,10 +127,10 @@ func TestCoordinationMessage_MarshalingRoundtrip(t *testing.T) { } tests := map[string]struct { - proposal coordinationProposal + proposal CoordinationProposal }{ "with noop proposal": { - proposal: &noopProposal{}, + proposal: &NoopProposal{}, }, "with heartbeat proposal": { proposal: &HeartbeatProposal{ @@ -301,7 +301,7 @@ func TestFuzzCoordinationMessage_MarshalingRoundtrip_WithNoopProposal(t *testing senderID group.MemberIndex coordinationBlock uint64 walletPublicKeyHash [20]byte - proposal noopProposal + proposal NoopProposal ) f := fuzz.New().NilChance(0.1). diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 2fe5847bd3..7527beb9a6 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -379,20 +379,12 @@ func (n *node) getCoordinationExecutor( return nil, false, fmt.Errorf("failed to get operator address: [%v]", err) } - proposalGenerator := func( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, - ) (coordinationProposal, error) { - // TODO: Implement proposal generation. - return &noopProposal{}, nil - } - executor := newCoordinationExecutor( n.chain, wallet, membersIndexes, operatorAddress, - proposalGenerator, + nil, // TODO: Implement proposal generation. broadcastChannel, membershipValidator, n.protocolLatch, @@ -817,7 +809,7 @@ func processCoordinationResult(node *node, result *coordinationResult) { // TODO: Record coordination faults. // TODO: Detect proposal type and run the appropriate handler. - switch result.proposal.actionType() { + switch result.proposal.ActionType() { case ActionHeartbeat: // node.handleHeartbeatRequest() case ActionDepositSweep: diff --git a/pkg/tbtc/node_test.go b/pkg/tbtc/node_test.go index 45797efc0b..fbdcac0dce 100644 --- a/pkg/tbtc/node_test.go +++ b/pkg/tbtc/node_test.go @@ -383,19 +383,19 @@ loop: t, "first result", ActionDepositSweep.String(), - processedResults[0].proposal.actionType().String(), + processedResults[0].proposal.ActionType().String(), ) testutils.AssertStringsEqual( t, "second result", ActionRedemption.String(), - processedResults[1].proposal.actionType().String(), + processedResults[1].proposal.ActionType().String(), ) testutils.AssertStringsEqual( t, "third result", ActionNoop.String(), - processedResults[2].proposal.actionType().String(), + processedResults[2].proposal.ActionType().String(), ) } @@ -403,11 +403,11 @@ type mockCoordinationProposal struct { action WalletActionType } -func (mcp *mockCoordinationProposal) actionType() WalletActionType { +func (mcp *mockCoordinationProposal) ActionType() WalletActionType { return mcp.action } -func (mcp *mockCoordinationProposal) validityBlocks() uint64 { +func (mcp *mockCoordinationProposal) ValidityBlocks() uint64 { panic("unsupported") } diff --git a/pkg/tbtc/redemption.go b/pkg/tbtc/redemption.go index 9cd9b7b79e..c675cf7313 100644 --- a/pkg/tbtc/redemption.go +++ b/pkg/tbtc/redemption.go @@ -60,11 +60,11 @@ type RedemptionProposal struct { RedemptionTxFee *big.Int } -func (rp *RedemptionProposal) actionType() WalletActionType { +func (rp *RedemptionProposal) ActionType() WalletActionType { return ActionRedemption } -func (rp *RedemptionProposal) validityBlocks() uint64 { +func (rp *RedemptionProposal) ValidityBlocks() uint64 { return redemptionProposalValidityBlocks } From 2ab464ded7403b78d6efb0cc7ea8a529d3863ebc Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 16:42:21 +0100 Subject: [PATCH 04/14] Implement proposal generator core Here we implement the core logic of the proposal generator. Specific proposal tasks will be added in the follow-up changesets. --- pkg/tbtcpg/tbtcpg.go | 99 ++++++++++++++++++ pkg/tbtcpg/tbtcpg_test.go | 214 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 pkg/tbtcpg/tbtcpg_test.go diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index 023a9db37d..27e3896824 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -1,7 +1,106 @@ package tbtcpg import ( + "fmt" "github.com/ipfs/go-log/v2" + "github.com/keep-network/keep-core/pkg/bitcoin" + "github.com/keep-network/keep-core/pkg/tbtc" + "go.uber.org/zap" + "golang.org/x/exp/slices" ) var logger = log.Logger("keep-tbtcpg") + +// proposalTask encapsulates logic used to generate an action proposal +// of the given type. +type proposalTask interface { + // run executes the task and returns a proposal, a boolean flag indicating + // whether the proposal was generated and an error if any. + run(walletPublicKeyHash [20]byte) (tbtc.CoordinationProposal, bool, error) + // actionType returns the type of the action proposal. + actionType() tbtc.WalletActionType +} + +// ProposalGenerator is a component responsible for generating coordination +// proposals for tbtc wallets. +type ProposalGenerator struct { + tasks []proposalTask +} + +// NewProposalGenerator returns a new proposal generator. +func NewProposalGenerator( + chain Chain, + btcChain bitcoin.Chain, +) *ProposalGenerator { + tasks := []proposalTask{ + // TODO: Fill with implementations. + } + + return &ProposalGenerator{ + tasks: tasks, + } +} + +// Generate generates a coordination proposal based on the given checklist +// of possible wallet actions. The checklist is a list of actions that +// should be checked for the given coordination window. This function returns +// a proposal for the first action from the checklist that is valid for the +// given wallet's state. If none of the actions are valid, the generator +// returns a no-op proposal. +func (pg *ProposalGenerator) Generate( + walletPublicKeyHash [20]byte, + actionsChecklist []tbtc.WalletActionType, +) (tbtc.CoordinationProposal, error) { + walletLogger := logger.With( + zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), + ) + + walletLogger.Info( + "starting proposal generation with tasks checklist [%v]", + actionsChecklist, + ) + + for _, action := range actionsChecklist { + walletLogger.Infof("starting proposal task [%s]", action) + + taskIndex := slices.IndexFunc(pg.tasks, func(task proposalTask) bool { + return task.actionType() == action + }) + + if taskIndex < 0 { + walletLogger.Warnf("proposal task [%s] is not supported", action) + continue + } + + proposal, ok, err := pg.tasks[taskIndex].run(walletPublicKeyHash) + if err != nil { + return nil, fmt.Errorf( + "error while running proposal task [%s]: [%v]", + action, + err, + ) + } + + if !ok { + walletLogger.Infof( + "proposal task [%s] completed without result", + action, + ) + continue + } + + walletLogger.Infof( + "proposal task [%s] completed with a result", + action, + ) + + return proposal, nil + } + + walletLogger.Infof( + "all proposal tasks completed without result; " + + "returning no-op proposal", + ) + + return &tbtc.NoopProposal{}, nil +} diff --git a/pkg/tbtcpg/tbtcpg_test.go b/pkg/tbtcpg/tbtcpg_test.go new file mode 100644 index 0000000000..41d5d96720 --- /dev/null +++ b/pkg/tbtcpg/tbtcpg_test.go @@ -0,0 +1,214 @@ +package tbtcpg + +import ( + "fmt" + "github.com/keep-network/keep-core/pkg/tbtc" + "reflect" + "testing" +) + +func TestProposalGenerator_Generate(t *testing.T) { + walletPublicKeyHash := [20]byte{1, 2, 3} + + tests := map[string]struct { + tasks []proposalTask + actionsChecklist []tbtc.WalletActionType + expectedProposal tbtc.CoordinationProposal + expectedErr error + }{ + "first task generates a proposal": { + tasks: []proposalTask{ + &mockProposalTask{ + action: tbtc.ActionRedemption, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + &mockProposalTask{ + action: tbtc.ActionDepositSweep, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + }, + actionsChecklist: []tbtc.WalletActionType{ + tbtc.ActionRedemption, + tbtc.ActionDepositSweep, + }, + expectedProposal: &mockCoordinationProposal{tbtc.ActionRedemption}, + }, + "subsequent task generates a proposal": { + tasks: []proposalTask{ + &mockProposalTask{ + action: tbtc.ActionRedemption, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultEmpty, + }, + }, + &mockProposalTask{ + action: tbtc.ActionDepositSweep, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + }, + actionsChecklist: []tbtc.WalletActionType{ + tbtc.ActionRedemption, + tbtc.ActionDepositSweep, + }, + expectedProposal: &mockCoordinationProposal{tbtc.ActionDepositSweep}, + }, + "first task returns error": { + tasks: []proposalTask{ + &mockProposalTask{ + action: tbtc.ActionRedemption, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultError, + }, + }, + &mockProposalTask{ + action: tbtc.ActionDepositSweep, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + }, + actionsChecklist: []tbtc.WalletActionType{ + tbtc.ActionRedemption, + tbtc.ActionDepositSweep, + }, + expectedProposal: nil, + expectedErr: fmt.Errorf("error while running proposal task [Redemption]: [proposal task error]"), + }, + "first task is unsupported": { + tasks: []proposalTask{ + &mockProposalTask{ + action: tbtc.ActionDepositSweep, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + }, + actionsChecklist: []tbtc.WalletActionType{ + tbtc.ActionRedemption, + tbtc.ActionDepositSweep, + }, + expectedProposal: &mockCoordinationProposal{tbtc.ActionDepositSweep}, + }, + "all tasks complete without result": { + tasks: []proposalTask{ + &mockProposalTask{ + action: tbtc.ActionRedemption, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultEmpty, + }, + }, + &mockProposalTask{ + action: tbtc.ActionDepositSweep, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultEmpty, + }, + }, + &mockProposalTask{ + action: tbtc.ActionHeartbeat, + results: map[[20]byte]mockProposalTaskResult{ + walletPublicKeyHash: resultProposal, + }, + }, + }, + actionsChecklist: []tbtc.WalletActionType{ + tbtc.ActionRedemption, + tbtc.ActionDepositSweep, + }, + expectedProposal: &tbtc.NoopProposal{}, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + generator := &ProposalGenerator{ + tasks: test.tasks, + } + + proposal, err := generator.Generate( + walletPublicKeyHash, + test.actionsChecklist, + ) + + if !reflect.DeepEqual(test.expectedErr, err) { + t.Errorf( + "unexpected error\nexpected: %v\nactual: %v", + test.expectedErr, + err, + ) + } + + if !reflect.DeepEqual(test.expectedProposal, proposal) { + t.Errorf( + "unexpected proposal\nexpected: %v\nactual: %v", + test.expectedProposal, + proposal, + ) + } + }) + } +} + +type mockProposalTaskResult uint8 + +const ( + resultProposal mockProposalTaskResult = iota + resultEmpty + resultError +) + +type mockProposalTask struct { + action tbtc.WalletActionType + results map[[20]byte]mockProposalTaskResult +} + +func (mpt *mockProposalTask) run(walletPublicKeyHash [20]byte) ( + tbtc.CoordinationProposal, + bool, + error, +) { + result, ok := mpt.results[walletPublicKeyHash] + if !ok { + panic("unexpected wallet public key hash") + } + + switch result { + case resultProposal: + return &mockCoordinationProposal{mpt.action}, true, nil + case resultEmpty: + return nil, false, nil + case resultError: + return nil, false, fmt.Errorf("proposal task error") + default: + panic("unexpected result") + } +} + +func (mpt *mockProposalTask) actionType() tbtc.WalletActionType { + return mpt.action +} + +type mockCoordinationProposal struct { + action tbtc.WalletActionType +} + +func (mcp *mockCoordinationProposal) ActionType() tbtc.WalletActionType { + return mcp.action +} + +func (mcp *mockCoordinationProposal) ValidityBlocks() uint64 { + panic("unsupported") +} + +func (mcp *mockCoordinationProposal) Marshal() ([]byte, error) { + panic("unsupported") +} + +func (mcp *mockCoordinationProposal) Unmarshal(bytes []byte) error { + panic("unsupported") +} From 3ff7af445d27893f0d6d0e6d6998cd2c3c667a6f Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 16:43:08 +0100 Subject: [PATCH 05/14] Update docstring of `CoordinationProposalGenerator.Generate` function --- pkg/tbtc/coordination.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tbtc/coordination.go b/pkg/tbtc/coordination.go index aa52c9942c..d03860a9dd 100644 --- a/pkg/tbtc/coordination.go +++ b/pkg/tbtc/coordination.go @@ -203,7 +203,7 @@ type CoordinationProposalGenerator interface { // should be checked for the given coordination window. The generator is // expected to return a proposal for the first action from the checklist // that is valid for the given wallet's state. If none of the actions are - // valid, the generator should return a noopProposal. + // valid, the generator should return a no-op proposal. Generate( walletPublicKeyHash [20]byte, actionsChecklist []WalletActionType, From f9c98913b09163d9d3f615e81b2d47c2f264f087 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 16:49:16 +0100 Subject: [PATCH 06/14] Wire up `pkg/tbtc` with `pkg/tbtcpg` Here we inject the `tbtcpg.ProposalGenerator` to the `tbtc.node` component. --- cmd/start.go | 7 +++++++ pkg/tbtc/node.go | 8 +++++++- pkg/tbtc/node_test.go | 3 +++ pkg/tbtc/signing_test.go | 1 + pkg/tbtc/tbtc.go | 2 ++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cmd/start.go b/cmd/start.go index b2af7a9d09..691d666e2f 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "github.com/keep-network/keep-core/pkg/tbtcpg" "github.com/keep-network/keep-common/pkg/persistence" "github.com/keep-network/keep-core/build" @@ -123,6 +124,11 @@ func start(cmd *cobra.Command) error { return fmt.Errorf("error initializing beacon: [%v]", err) } + proposalGenerator := tbtcpg.NewProposalGenerator( + tbtcChain, + btcChain, + ) + err = tbtc.Initialize( ctx, tbtcChain, @@ -131,6 +137,7 @@ func start(cmd *cobra.Command) error { tbtcKeyStorePersistence, tbtcDataPersistence, scheduler, + proposalGenerator, clientConfig.Tbtc, clientInfoRegistry, ) diff --git a/pkg/tbtc/node.go b/pkg/tbtc/node.go index 7527beb9a6..b4e167a5b4 100644 --- a/pkg/tbtc/node.go +++ b/pkg/tbtc/node.go @@ -85,6 +85,10 @@ type node struct { // // coordinationExecutors MUST NOT be used outside this struct. coordinationExecutors map[string]*coordinationExecutor + + // proposalGenerator is the implementation of the coordination proposal + // generator used by the node. + proposalGenerator CoordinationProposalGenerator } func newNode( @@ -95,6 +99,7 @@ func newNode( keyStorePersistance persistence.ProtectedHandle, workPersistence persistence.BasicHandle, scheduler *generator.Scheduler, + proposalGenerator CoordinationProposalGenerator, config Config, ) (*node, error) { walletRegistry := newWalletRegistry(keyStorePersistance) @@ -112,6 +117,7 @@ func newNode( protocolLatch: latch, signingExecutors: make(map[string]*signingExecutor), coordinationExecutors: make(map[string]*coordinationExecutor), + proposalGenerator: proposalGenerator, } // Only the operator address is known at this point and can be pre-fetched. @@ -384,7 +390,7 @@ func (n *node) getCoordinationExecutor( wallet, membersIndexes, operatorAddress, - nil, // TODO: Implement proposal generation. + n.proposalGenerator, broadcastChannel, membershipValidator, n.protocolLatch, diff --git a/pkg/tbtc/node_test.go b/pkg/tbtc/node_test.go index fbdcac0dce..b9dbb01992 100644 --- a/pkg/tbtc/node_test.go +++ b/pkg/tbtc/node_test.go @@ -43,6 +43,7 @@ func TestNode_GetSigningExecutor(t *testing.T) { keyStorePersistence, &mockPersistenceHandle{}, generator.StartScheduler(), + &mockCoordinationProposalGenerator{}, Config{}, ) if err != nil { @@ -160,6 +161,7 @@ func TestNode_GetCoordinationExecutor(t *testing.T) { keyStorePersistence, &mockPersistenceHandle{}, generator.StartScheduler(), + &mockCoordinationProposalGenerator{}, Config{}, ) if err != nil { @@ -282,6 +284,7 @@ func TestNode_RunCoordinationLayer(t *testing.T) { keyStorePersistence, &mockPersistenceHandle{}, generator.StartScheduler(), + &mockCoordinationProposalGenerator{}, Config{}, ) if err != nil { diff --git a/pkg/tbtc/signing_test.go b/pkg/tbtc/signing_test.go index 918136478a..1ace228c72 100644 --- a/pkg/tbtc/signing_test.go +++ b/pkg/tbtc/signing_test.go @@ -169,6 +169,7 @@ func setupSigningExecutor(t *testing.T) *signingExecutor { keyStorePersistence, &mockPersistenceHandle{}, generator.StartScheduler(), + &mockCoordinationProposalGenerator{}, Config{}, ) if err != nil { diff --git a/pkg/tbtc/tbtc.go b/pkg/tbtc/tbtc.go index f36f6bfe00..a3e0ccb010 100644 --- a/pkg/tbtc/tbtc.go +++ b/pkg/tbtc/tbtc.go @@ -78,6 +78,7 @@ func Initialize( keyStorePersistence persistence.ProtectedHandle, workPersistence persistence.BasicHandle, scheduler *generator.Scheduler, + proposalGenerator CoordinationProposalGenerator, config Config, clientInfo *clientinfo.Registry, ) error { @@ -95,6 +96,7 @@ func Initialize( keyStorePersistence, workPersistence, scheduler, + proposalGenerator, config, ) if err != nil { From 9244e6ca9877c4db51fea9974316b4bb5fba7e70 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 16:56:29 +0100 Subject: [PATCH 07/14] Introduce `depositSweepTask` component Here we introduce the implementation of the `tbtcpg.proposalTask` for deposit sweeps. --- pkg/tbtcpg/chain.go | 2 + pkg/tbtcpg/deposit_sweep.go | 91 ++++++++++++++++++++++++++------ pkg/tbtcpg/deposit_sweep_test.go | 10 ++-- pkg/tbtcpg/tbtcpg.go | 7 ++- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/pkg/tbtcpg/chain.go b/pkg/tbtcpg/chain.go index 310e3ad0db..557d1d8a54 100644 --- a/pkg/tbtcpg/chain.go +++ b/pkg/tbtcpg/chain.go @@ -50,6 +50,8 @@ type Chain interface { // SubmitDepositSweepProposalWithReimbursement submits a deposit sweep // proposal to the chain. It reimburses the gas cost to the caller. + // + // TODO: Remove this method. SubmitDepositSweepProposalWithReimbursement( proposal *tbtc.DepositSweepProposal, ) error diff --git a/pkg/tbtcpg/deposit_sweep.go b/pkg/tbtcpg/deposit_sweep.go index 4869157d85..712059aa5e 100644 --- a/pkg/tbtcpg/deposit_sweep.go +++ b/pkg/tbtcpg/deposit_sweep.go @@ -13,6 +13,74 @@ import ( const depositScriptByteSize = 92 +// depositSweepTask is a task that may produce a deposit sweep proposal. +type depositSweepTask struct { + chain Chain + btcChain bitcoin.Chain +} + +func newDepositSweepTask( + chain Chain, + btcChain bitcoin.Chain, +) *depositSweepTask { + return &depositSweepTask{ + chain: chain, + btcChain: btcChain, + } +} + +func (dst *depositSweepTask) run(walletPublicKeyHash [20]byte) ( + tbtc.CoordinationProposal, + bool, + error, +) { + depositSweepMaxSize, err := dst.chain.GetDepositSweepMaxSize() + if err != nil { + return nil, false, fmt.Errorf( + "failed to get deposit sweep max size: [%w]", + err, + ) + } + + _, deposits, err := FindDepositsToSweep( + dst.chain, + dst.btcChain, + walletPublicKeyHash, + depositSweepMaxSize, + ) + if err != nil { + return nil, false, fmt.Errorf( + "cannot find deposits to sweep: [%w]", + err, + ) + } + + if len(deposits) == 0 { + logger.Info("no deposits to sweep") + return nil, false, nil + } + + proposal, err := ProposeDepositsSweep( + dst.chain, + dst.btcChain, + walletPublicKeyHash, + 0, + deposits, + ) + if err != nil { + return nil, false, fmt.Errorf( + "cannot prepare deposit sweep proposal: [%w]", + err, + ) + } + + return proposal, true, nil +} + +func (dst *depositSweepTask) actionType() tbtc.WalletActionType { + return tbtc.ActionDepositSweep +} + // DepositReference holds some data allowing to identify and refer to a deposit. type DepositReference struct { FundingTxHash bitcoin.Hash @@ -289,17 +357,16 @@ func FindDepositsToSweep( return walletPublicKeyHash, depositsRefs, nil } -// ProposeDepositsSweep handles deposit sweep proposal request submission. +// ProposeDepositsSweep returns a deposit sweep proposal. func ProposeDepositsSweep( chain Chain, btcChain bitcoin.Chain, walletPublicKeyHash [20]byte, fee int64, deposits []*DepositReference, - dryRun bool, -) error { +) (*tbtc.DepositSweepProposal, error) { if len(deposits) == 0 { - return fmt.Errorf("deposits list is empty") + return nil, fmt.Errorf("deposits list is empty") } // Estimate fee if it's missing. @@ -308,7 +375,7 @@ func ProposeDepositsSweep( var err error _, _, perDepositMaxFee, _, err := chain.GetDepositParameters() if err != nil { - return fmt.Errorf("cannot get deposit tx max fee: [%w]", err) + return nil, fmt.Errorf("cannot get deposit tx max fee: [%w]", err) } estimatedFee, _, err := estimateDepositsSweepFee( @@ -317,7 +384,7 @@ func ProposeDepositsSweep( perDepositMaxFee, ) if err != nil { - return fmt.Errorf("cannot estimate sweep transaction fee: [%v]", err) + return nil, fmt.Errorf("cannot estimate sweep transaction fee: [%v]", err) } fee = estimatedFee @@ -353,6 +420,7 @@ func ProposeDepositsSweep( } logger.Infof("validating the deposit sweep proposal...") + if _, err := tbtc.ValidateDepositSweepProposal( logger, proposal, @@ -360,17 +428,10 @@ func ProposeDepositsSweep( chain, btcChain, ); err != nil { - return fmt.Errorf("failed to verify deposit sweep proposal: %v", err) - } - - if !dryRun { - logger.Infof("submitting the deposit sweep proposal...") - if err := chain.SubmitDepositSweepProposalWithReimbursement(proposal); err != nil { - return fmt.Errorf("failed to submit deposit sweep proposal: %v", err) - } + return nil, fmt.Errorf("failed to verify deposit sweep proposal: %v", err) } - return nil + return proposal, nil } // EstimateDepositsSweepFee computes the total fee for the Bitcoin deposits diff --git a/pkg/tbtcpg/deposit_sweep_test.go b/pkg/tbtcpg/deposit_sweep_test.go index 843beb385c..97c2a48e52 100644 --- a/pkg/tbtcpg/deposit_sweep_test.go +++ b/pkg/tbtcpg/deposit_sweep_test.go @@ -152,13 +152,12 @@ func TestProposeDepositsSweep(t *testing.T) { btcChain.SetEstimateSatPerVByteFee(1, scenario.EstimateSatPerVByteFee) // Test execution. - err = tbtcpg.ProposeDepositsSweep( + proposal, err := tbtcpg.ProposeDepositsSweep( tbtcChain, btcChain, scenario.WalletPublicKeyHash, scenario.SweepTxFee, scenario.DepositsReferences(), - false, ) if !reflect.DeepEqual(scenario.ExpectedErr, err) { @@ -171,13 +170,18 @@ func TestProposeDepositsSweep(t *testing.T) { ) } + var actualDepositSweepProposals []*tbtc.DepositSweepProposal + if proposal != nil { + actualDepositSweepProposals = append(actualDepositSweepProposals, proposal) + } + var expectedDepositSweepProposals []*tbtc.DepositSweepProposal if p := scenario.ExpectedDepositSweepProposal; p != nil { expectedDepositSweepProposals = append(expectedDepositSweepProposals, p) } if diff := deep.Equal( - tbtcChain.DepositSweepProposals(), + actualDepositSweepProposals, expectedDepositSweepProposals, ); diff != nil { t.Errorf("invalid deposits: %v", diff) diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index 27e3896824..a99234ef9f 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -33,7 +33,12 @@ func NewProposalGenerator( btcChain bitcoin.Chain, ) *ProposalGenerator { tasks := []proposalTask{ - // TODO: Fill with implementations. + newDepositSweepTask(chain, btcChain), + // newRedemptionTask(chain, btcChain), + // newHeartbeatTask(chain, btcChain), + // TODO: Uncomment when moving funds support is implemented. + // newMovedFundsSweepTask(), + // newMovingFundsTask(), } return &ProposalGenerator{ From eee6ed046f9894d90a468ae921fa95a24102f599 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 30 Nov 2023 17:11:38 +0100 Subject: [PATCH 08/14] Introduce `redemptionTask` component Here we introduce the implementation of the `tbtcpg.proposalTask` for redemptions. --- pkg/tbtcpg/redemptions.go | 92 ++++++++++++++++++++++++++++------ pkg/tbtcpg/redemptions_test.go | 5 +- pkg/tbtcpg/tbtcpg.go | 2 +- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/pkg/tbtcpg/redemptions.go b/pkg/tbtcpg/redemptions.go index af40695d7e..8478778055 100644 --- a/pkg/tbtcpg/redemptions.go +++ b/pkg/tbtcpg/redemptions.go @@ -13,6 +13,76 @@ import ( "github.com/keep-network/keep-core/pkg/tbtc" ) +// redemptionTask is a task that may produce a redemption proposal. +type redemptionTask struct { + chain Chain + btcChain bitcoin.Chain +} + +func newRedemptionTask( + chain Chain, + btcChain bitcoin.Chain, +) *redemptionTask { + return &redemptionTask{ + chain: chain, + btcChain: btcChain, + } +} + +func (rt *redemptionTask) run(walletPublicKeyHash [20]byte) ( + tbtc.CoordinationProposal, + bool, + error, +) { + redemptionMaxSize, err := rt.chain.GetRedemptionMaxSize() + if err != nil { + return nil, false, fmt.Errorf( + "failed to get redemption max size: [%w]", + err, + ) + } + + walletsPendingRedemptions, err := FindPendingRedemptions( + rt.chain, + PendingRedemptionsFilter{ + WalletPublicKeyHashes: [][20]byte{walletPublicKeyHash}, + RequestsLimit: redemptionMaxSize, + }, + ) + if err != nil { + return nil, false, fmt.Errorf( + "cannot find pending redemption requests: [%w]", + err, + ) + } + + redeemersOutputScripts, ok := walletsPendingRedemptions[walletPublicKeyHash] + if !ok { + logger.Info("no pending redemption requests") + return nil, false, nil + } + + proposal, err := ProposeRedemption( + rt.chain, + rt.btcChain, + walletPublicKeyHash, + 0, + redeemersOutputScripts, + ) + if err != nil { + return nil, false, fmt.Errorf( + "cannot prepare redemption proposal: [%w]", + err, + ) + } + + return proposal, true, nil +} + +func (rt *redemptionTask) actionType() tbtc.WalletActionType { + return tbtc.ActionRedemption +} + // RedemptionRequest represents a redemption request. type RedemptionRequest struct { WalletPublicKeyHash [20]byte @@ -222,17 +292,16 @@ func FindPendingRedemptions( return result, nil } -// ProposeRedemption handles redemption proposal submission. +// ProposeRedemption returns a redemption proposal. func ProposeRedemption( chain Chain, btcChain bitcoin.Chain, walletPublicKeyHash [20]byte, fee int64, redeemersOutputScripts []bitcoin.Script, - dryRun bool, -) error { +) (*tbtc.RedemptionProposal, error) { if len(redeemersOutputScripts) == 0 { - return fmt.Errorf("redemptions list is empty") + return nil, fmt.Errorf("redemptions list is empty") } logger.Infof( @@ -252,7 +321,7 @@ func ProposeRedemption( redeemersOutputScripts, ) if err != nil { - return fmt.Errorf( + return nil, fmt.Errorf( "cannot estimate redemption transaction fee: [%w]", err, ) @@ -278,19 +347,10 @@ func ProposeRedemption( proposal, chain, ); err != nil { - return fmt.Errorf("failed to verify redemption proposal: %v", err) - } - - if !dryRun { - logger.Infof("submitting the redemption proposal...") - if err := chain.SubmitRedemptionProposalWithReimbursement(proposal); err != nil { - return fmt.Errorf("failed to submit redemption proposal: %v", err) - } - } else { - logger.Infof("skipping transaction submission in dry-run mode") + return nil, fmt.Errorf("failed to verify redemption proposal: %v", err) } - return nil + return proposal, nil } func getPendingRedemptions( diff --git a/pkg/tbtcpg/redemptions_test.go b/pkg/tbtcpg/redemptions_test.go index 781b4722a9..48f6a9ddec 100644 --- a/pkg/tbtcpg/redemptions_test.go +++ b/pkg/tbtcpg/redemptions_test.go @@ -202,20 +202,19 @@ func TestProposeRedemption(t *testing.T) { t.Fatal(err) } - err = tbtcpg.ProposeRedemption( + proposal, err := tbtcpg.ProposeRedemption( tbtcChain, btcChain, walletPublicKeyHash, test.fee, redeemersOutputScripts, - false, ) if err != nil { t.Fatal(err) } if diff := deep.Equal( - tbtcChain.RedemptionProposals(), + []*tbtc.RedemptionProposal{proposal}, []*tbtc.RedemptionProposal{test.expectedProposal}, ); diff != nil { t.Errorf("invalid deposits: %v", diff) diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index a99234ef9f..9831ee35a1 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -34,7 +34,7 @@ func NewProposalGenerator( ) *ProposalGenerator { tasks := []proposalTask{ newDepositSweepTask(chain, btcChain), - // newRedemptionTask(chain, btcChain), + newRedemptionTask(chain, btcChain), // newHeartbeatTask(chain, btcChain), // TODO: Uncomment when moving funds support is implemented. // newMovedFundsSweepTask(), From 01781253e6528240894e9fd7868b61c10c641b1d Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 1 Dec 2023 15:19:31 +0100 Subject: [PATCH 09/14] Refactoring and cleanup of tasks code Here we adjust the tasks code to the current requirements. Redemptions and deposits are now fetched for specific wallet. There is no need to maintain the possibility to do so for multiple wallets in the same time. Because of that, we are simplifying the code. By the way, we are adjusting logging and tests. --- pkg/tbtcpg/deposit_sweep.go | 240 ++++++-------- pkg/tbtcpg/deposit_sweep_test.go | 52 +-- pkg/tbtcpg/internal/test/marshaling.go | 86 ++--- pkg/tbtcpg/internal/test/tbtcpgtest.go | 27 +- .../testdata/find_deposits_scenario_0.json | 63 +--- .../testdata/find_deposits_scenario_1.json | 33 +- .../testdata/find_deposits_scenario_2.json | 38 +-- .../testdata/find_deposits_scenario_3.json | 39 --- .../find_pending_redemptions_scenario_0.json | 42 +-- .../find_pending_redemptions_scenario_1.json | 40 +-- .../find_pending_redemptions_scenario_2.json | 37 +-- .../find_pending_redemptions_scenario_3.json | 78 ----- .../find_pending_redemptions_scenario_4.json | 77 ----- .../find_pending_redemptions_scenario_5.json | 79 ----- .../testdata/propose_sweep_scenario_0.json | 2 +- .../testdata/propose_sweep_scenario_1.json | 2 +- .../testdata/propose_sweep_scenario_2.json | 2 +- pkg/tbtcpg/redemptions.go | 305 +++++------------- pkg/tbtcpg/redemptions_test.go | 45 +-- pkg/tbtcpg/tbtcpg.go | 26 +- pkg/tbtcpg/tbtcpg_test.go | 16 +- 21 files changed, 325 insertions(+), 1004 deletions(-) delete mode 100644 pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json delete mode 100644 pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json delete mode 100644 pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json delete mode 100644 pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json diff --git a/pkg/tbtcpg/deposit_sweep.go b/pkg/tbtcpg/deposit_sweep.go index 712059aa5e..8df52b76d5 100644 --- a/pkg/tbtcpg/deposit_sweep.go +++ b/pkg/tbtcpg/deposit_sweep.go @@ -2,6 +2,8 @@ package tbtcpg import ( "fmt" + "github.com/ipfs/go-log/v2" + "go.uber.org/zap" "math" "math/big" "sort" @@ -13,27 +15,32 @@ import ( const depositScriptByteSize = 92 -// depositSweepTask is a task that may produce a deposit sweep proposal. -type depositSweepTask struct { +// DepositSweepTask is a task that may produce a deposit sweep proposal. +type DepositSweepTask struct { chain Chain btcChain bitcoin.Chain } -func newDepositSweepTask( +func NewDepositSweepTask( chain Chain, btcChain bitcoin.Chain, -) *depositSweepTask { - return &depositSweepTask{ +) *DepositSweepTask { + return &DepositSweepTask{ chain: chain, btcChain: btcChain, } } -func (dst *depositSweepTask) run(walletPublicKeyHash [20]byte) ( +func (dst *DepositSweepTask) Run(walletPublicKeyHash [20]byte) ( tbtc.CoordinationProposal, bool, error, ) { + taskLogger := logger.With( + zap.String("task", dst.ActionType().String()), + zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), + ) + depositSweepMaxSize, err := dst.chain.GetDepositSweepMaxSize() if err != nil { return nil, false, fmt.Errorf( @@ -42,9 +49,8 @@ func (dst *depositSweepTask) run(walletPublicKeyHash [20]byte) ( ) } - _, deposits, err := FindDepositsToSweep( - dst.chain, - dst.btcChain, + deposits, err := dst.FindDepositsToSweep( + taskLogger, walletPublicKeyHash, depositSweepMaxSize, ) @@ -56,16 +62,15 @@ func (dst *depositSweepTask) run(walletPublicKeyHash [20]byte) ( } if len(deposits) == 0 { - logger.Info("no deposits to sweep") + taskLogger.Info("no deposits to sweep") return nil, false, nil } - proposal, err := ProposeDepositsSweep( - dst.chain, - dst.btcChain, + proposal, err := dst.ProposeDepositsSweep( + taskLogger, walletPublicKeyHash, - 0, deposits, + 0, ) if err != nil { return nil, false, fmt.Errorf( @@ -77,7 +82,7 @@ func (dst *depositSweepTask) run(walletPublicKeyHash [20]byte) ( return proposal, true, nil } -func (dst *depositSweepTask) actionType() tbtc.WalletActionType { +func (dst *DepositSweepTask) ActionType() tbtc.WalletActionType { return tbtc.ActionDepositSweep } @@ -109,7 +114,28 @@ func FindDeposits( skipSwept bool, skipUnconfirmed bool, ) ([]*Deposit, error) { - logger.Infof("reading revealed deposits from chain...") + return findDeposits( + logger, + chain, + btcChain, + walletPublicKeyHash, + maxNumberOfDeposits, + skipSwept, + skipUnconfirmed, + ) +} + +// findDeposits finds deposits according to the given criteria. +func findDeposits( + fnLogger log.StandardLogger, + chain Chain, + btcChain bitcoin.Chain, + walletPublicKeyHash [20]byte, + maxNumberOfDeposits int, + skipSwept bool, + skipUnconfirmed bool, +) ([]*Deposit, error) { + fnLogger.Infof("reading revealed deposits from chain") filter := &tbtc.DepositRevealedEventFilter{} if walletPublicKeyHash != [20]byte{} { @@ -124,14 +150,14 @@ func FindDeposits( ) } - logger.Infof("found %d DepositRevealed events", len(depositRevealedEvents)) + fnLogger.Infof("found [%d] DepositRevealed events", len(depositRevealedEvents)) // Take the oldest first sort.SliceStable(depositRevealedEvents, func(i, j int) bool { return depositRevealedEvents[i].BlockNumber < depositRevealedEvents[j].BlockNumber }) - logger.Infof("getting deposits details...") + fnLogger.Infof("getting deposits details") resultSliceCapacity := len(depositRevealedEvents) if maxNumberOfDeposits > 0 { @@ -139,14 +165,15 @@ func FindDeposits( } result := make([]*Deposit, 0, resultSliceCapacity) - for i, event := range depositRevealedEvents { + for _, event := range depositRevealedEvents { if len(result) == cap(result) { break } - logger.Debugf("getting details of deposit %d/%d", i+1, len(depositRevealedEvents)) - depositKey := chain.BuildDepositKey(event.FundingTxHash, event.FundingOutputIndex) + depositKeyStr := depositKey.Text(16) + + fnLogger.Debugf("getting details of deposit [%s]", depositKeyStr) depositRequest, found, err := chain.GetDepositRequest( event.FundingTxHash, @@ -161,30 +188,32 @@ func FindDeposits( if !found { return nil, fmt.Errorf( - "no deposit request for key [0x%x]", - depositKey.Text(16), + "no deposit request for key [%s]", + depositKeyStr, ) } isSwept := depositRequest.SweptAt.Unix() != 0 if skipSwept && isSwept { - logger.Debugf("deposit %d/%d is already swept", i+1, len(depositRevealedEvents)) + fnLogger.Debugf("deposit [%s] is already swept", depositKeyStr) continue } confirmations, err := btcChain.GetTransactionConfirmations(event.FundingTxHash) if err != nil { - logger.Errorf( + fnLogger.Errorf( "failed to get bitcoin transaction confirmations: [%v]", err, ) } if skipUnconfirmed && confirmations < tbtc.DepositSweepRequiredFundingTxConfirmations { - logger.Debugf( - "deposit %d/%d funding transaction doesn't have enough confirmations: %d/%d", - i+1, len(depositRevealedEvents), - confirmations, tbtc.DepositSweepRequiredFundingTxConfirmations) + fnLogger.Debugf( + "deposit [%s] funding transaction doesn't have enough confirmations: [%d/%d]", + depositKeyStr, + confirmations, + tbtc.DepositSweepRequiredFundingTxConfirmations, + ) continue } @@ -193,7 +222,7 @@ func FindDeposits( event.FundingTxHash, ) if err != nil { - logger.Errorf( + fnLogger.Errorf( "failed to get deposit transaction data: [%v]", err, ) @@ -224,121 +253,55 @@ func FindDeposits( } // FindDepositsToSweep finds deposits that can be swept. -// If a wallet public key hash is provided, it will find unswept deposits for the -// given wallet. If a wallet public key hash is nil, it will check all wallets -// starting from the oldest one to find a first wallet containing unswept deposits -// and return those deposits. // maxNumberOfDeposits is used as a ceiling for the number of deposits in the // result. If number of discovered deposits meets the maxNumberOfDeposits the // function will stop fetching more deposits. -// This function will return a wallet public key hash and a list of deposits from -// the wallet that can be swept. +// This function will return a list of deposits from the wallet that can be swept. // Deposits with insufficient number of funding transaction confirmations will // not be taken into consideration for sweeping. -// The result will not mix deposits for different wallets. // // TODO: Cache immutable data -func FindDepositsToSweep( - chain Chain, - btcChain bitcoin.Chain, +func (dst *DepositSweepTask) FindDepositsToSweep( + taskLogger log.StandardLogger, walletPublicKeyHash [20]byte, maxNumberOfDeposits uint16, -) ([20]byte, []*DepositReference, error) { - logger.Infof("deposit sweep max size: %d", maxNumberOfDeposits) - - getDepositsToSweepFromWallet := func(walletToSweep [20]byte) ([]*Deposit, error) { - unsweptDeposits, err := FindDeposits( - chain, - btcChain, - walletToSweep, - int(maxNumberOfDeposits), - true, - true, - ) - if err != nil { - return nil, - fmt.Errorf( - "failed to get deposits for [%s] wallet: [%w]", - hexutils.Encode(walletToSweep[:]), - err, - ) - } - return unsweptDeposits, nil - } - - var depositsToSweep []*Deposit - // If walletPublicKeyHash is not provided we need to find a wallet that has - // unswept deposits. +) ([]*DepositReference, error) { if walletPublicKeyHash == [20]byte{} { - walletRegisteredEvents, err := chain.PastNewWalletRegisteredEvents(nil) - if err != nil { - return [20]byte{}, nil, fmt.Errorf("failed to get registered wallets: [%w]", err) - } - - // Take the oldest first - sort.SliceStable(walletRegisteredEvents, func(i, j int) bool { - return walletRegisteredEvents[i].BlockNumber < walletRegisteredEvents[j].BlockNumber - }) - - sweepingWallets := walletRegisteredEvents - // Only two the most recently created wallets are sweeping. - if len(walletRegisteredEvents) >= 2 { - sweepingWallets = walletRegisteredEvents[len(walletRegisteredEvents)-2:] - } + return nil, fmt.Errorf("wallet public key hash is required") + } - for _, registeredWallet := range sweepingWallets { - logger.Infof( - "fetching deposits from wallet [%s]...", - hexutils.Encode(registeredWallet.WalletPublicKeyHash[:]), - ) + taskLogger.Infof("fetching max [%d] deposits", maxNumberOfDeposits) - unsweptDeposits, err := getDepositsToSweepFromWallet( - registeredWallet.WalletPublicKeyHash, - ) - if err != nil { - return [20]byte{}, nil, err - } - - // Check if there are any unswept deposits in this wallet. If so - // sweep this wallet and don't check the other wallet. - if len(unsweptDeposits) > 0 { - walletPublicKeyHash = registeredWallet.WalletPublicKeyHash - depositsToSweep = unsweptDeposits - break - } - } - } else { - logger.Infof( - "fetching deposits from wallet [%s]...", - hexutils.Encode(walletPublicKeyHash[:]), - ) - unsweptDeposits, err := getDepositsToSweepFromWallet( - walletPublicKeyHash, - ) - if err != nil { - return [20]byte{}, nil, err - } - depositsToSweep = unsweptDeposits + unsweptDeposits, err := findDeposits( + taskLogger, + dst.chain, + dst.btcChain, + walletPublicKeyHash, + int(maxNumberOfDeposits), + true, + true, + ) + if err != nil { + return nil, err } + depositsToSweep := unsweptDeposits + if len(depositsToSweep) == 0 { - return [20]byte{}, nil, nil + return nil, nil } - logger.Infof( - "found [%d] deposits to sweep for wallet [%s]", + taskLogger.Infof( + "found [%d] deposits to sweep", len(depositsToSweep), - hexutils.Encode(walletPublicKeyHash[:]), ) - for i, deposit := range depositsToSweep { - logger.Infof( - "deposit [%d/%d] - %s", - i+1, - len(depositsToSweep), + for _, deposit := range depositsToSweep { + taskLogger.Infof( + "deposit [%s] - [%s]", + deposit.DepositKey, fmt.Sprintf( - "depositKey: [%s], reveal block: [%d], funding transaction: [%s], output index: [%d]", - deposit.DepositKey, + "reveal block: [%d], funding transaction: [%s], output index: [%d]", deposit.RevealBlock, deposit.FundingTxHash.Hex(bitcoin.ReversedByteOrder), deposit.FundingOutputIndex, @@ -354,32 +317,33 @@ func FindDepositsToSweep( } } - return walletPublicKeyHash, depositsRefs, nil + return depositsRefs, nil } // ProposeDepositsSweep returns a deposit sweep proposal. -func ProposeDepositsSweep( - chain Chain, - btcChain bitcoin.Chain, +func (dst *DepositSweepTask) ProposeDepositsSweep( + taskLogger log.StandardLogger, walletPublicKeyHash [20]byte, - fee int64, deposits []*DepositReference, + fee int64, ) (*tbtc.DepositSweepProposal, error) { if len(deposits) == 0 { return nil, fmt.Errorf("deposits list is empty") } + taskLogger.Infof("preparing a deposit sweep proposal") + // Estimate fee if it's missing. if fee <= 0 { - logger.Infof("estimating sweep transaction fee...") + taskLogger.Infof("estimating sweep transaction fee") var err error - _, _, perDepositMaxFee, _, err := chain.GetDepositParameters() + _, _, perDepositMaxFee, _, err := dst.chain.GetDepositParameters() if err != nil { return nil, fmt.Errorf("cannot get deposit tx max fee: [%w]", err) } estimatedFee, _, err := estimateDepositsSweepFee( - btcChain, + dst.btcChain, len(deposits), perDepositMaxFee, ) @@ -390,9 +354,7 @@ func ProposeDepositsSweep( fee = estimatedFee } - logger.Infof("sweep transaction fee: [%d]", fee) - - logger.Infof("preparing a deposit sweep proposal...") + taskLogger.Infof("sweep transaction fee: [%d]", fee) depositsKeys := make([]struct { FundingTxHash bitcoin.Hash @@ -419,14 +381,14 @@ func ProposeDepositsSweep( DepositsRevealBlocks: depositsRevealBlocks, } - logger.Infof("validating the deposit sweep proposal...") + taskLogger.Infof("validating the deposit sweep proposal") if _, err := tbtc.ValidateDepositSweepProposal( - logger, + taskLogger, proposal, tbtc.DepositSweepRequiredFundingTxConfirmations, - chain, - btcChain, + dst.chain, + dst.btcChain, ); err != nil { return nil, fmt.Errorf("failed to verify deposit sweep proposal: %v", err) } diff --git a/pkg/tbtcpg/deposit_sweep_test.go b/pkg/tbtcpg/deposit_sweep_test.go index 97c2a48e52..af7faad986 100644 --- a/pkg/tbtcpg/deposit_sweep_test.go +++ b/pkg/tbtcpg/deposit_sweep_test.go @@ -1,19 +1,19 @@ package tbtcpg_test import ( + "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/tbtcpg" "reflect" "testing" "github.com/go-test/deep" "github.com/ipfs/go-log" - "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" - "github.com/keep-network/keep-core/pkg/tbtcpg" "github.com/keep-network/keep-core/pkg/tbtcpg/internal/test" ) -func TestFindDepositsToSweep(t *testing.T) { +func TestDepositSweepTask_FindDepositsToSweep(t *testing.T) { err := log.SetLogLevel("*", "DEBUG") if err != nil { t.Fatal(err) @@ -29,22 +29,7 @@ func TestFindDepositsToSweep(t *testing.T) { tbtcChain := tbtcpg.NewLocalChain() btcChain := tbtcpg.NewLocalBitcoinChain() - expectedWallet := scenario.ExpectedWalletPublicKeyHash - // Chain setup. - for _, wallet := range scenario.Wallets { - err := tbtcChain.AddPastNewWalletRegisteredEvent( - nil, - &tbtc.NewWalletRegisteredEvent{ - WalletPublicKeyHash: wallet.WalletPublicKeyHash, - BlockNumber: wallet.RegistrationBlockNumber, - }, - ) - if err != nil { - t.Fatal(err) - } - } - for _, deposit := range scenario.Deposits { tbtcChain.SetDepositRequest( deposit.FundingTxHash, @@ -71,10 +56,11 @@ func TestFindDepositsToSweep(t *testing.T) { } } + task := tbtcpg.NewDepositSweepTask(tbtcChain, btcChain) + // Test execution. - actualWallet, actualDeposits, err := tbtcpg.FindDepositsToSweep( - tbtcChain, - btcChain, + actualDeposits, err := task.FindDepositsToSweep( + &testutils.MockLogger{}, scenario.WalletPublicKeyHash, scenario.MaxNumberOfDeposits, ) @@ -83,22 +69,17 @@ func TestFindDepositsToSweep(t *testing.T) { t.Fatal(err) } - if actualWallet != expectedWallet { - t.Errorf( - "invalid wallet public key hash\nexpected: %s\nactual: %s", - hexutils.Encode(expectedWallet[:]), - hexutils.Encode(actualWallet[:]), - ) - } - - if diff := deep.Equal(actualDeposits, scenario.ExpectedUnsweptDeposits); diff != nil { + if diff := deep.Equal( + scenario.ExpectedUnsweptDeposits, + actualDeposits, + ); diff != nil { t.Errorf("invalid deposits: %v", diff) } }) } } -func TestProposeDepositsSweep(t *testing.T) { +func TestDepositSweepTask_ProposeDepositsSweep(t *testing.T) { err := log.SetLogLevel("*", "DEBUG") if err != nil { t.Fatal(err) @@ -151,13 +132,14 @@ func TestProposeDepositsSweep(t *testing.T) { btcChain.SetEstimateSatPerVByteFee(1, scenario.EstimateSatPerVByteFee) + task := tbtcpg.NewDepositSweepTask(tbtcChain, btcChain) + // Test execution. - proposal, err := tbtcpg.ProposeDepositsSweep( - tbtcChain, - btcChain, + proposal, err := task.ProposeDepositsSweep( + &testutils.MockLogger{}, scenario.WalletPublicKeyHash, - scenario.SweepTxFee, scenario.DepositsReferences(), + scenario.SweepTxFee, ) if !reflect.DeepEqual(scenario.ExpectedErr, err) { diff --git a/pkg/tbtcpg/internal/test/marshaling.go b/pkg/tbtcpg/internal/test/marshaling.go index 70f77d20cd..b5bc17dff9 100644 --- a/pkg/tbtcpg/internal/test/marshaling.go +++ b/pkg/tbtcpg/internal/test/marshaling.go @@ -4,11 +4,10 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/keep-network/keep-core/pkg/tbtcpg" "math/big" "time" - "github.com/keep-network/keep-core/pkg/tbtcpg" - "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" @@ -21,11 +20,7 @@ func (dsts *FindDepositsToSweepTestScenario) UnmarshalJSON(data []byte) error { Title string WalletPublicKeyHash string MaxNumberOfDeposits uint16 - Wallets []struct { - WalletPublicKeyHash string - RegistrationBlockNumber uint64 - } - Deposits []struct { + Deposits []struct { FundingTxHash string FundingOutputIndex uint32 FundingTxConfirmations uint @@ -34,8 +29,7 @@ func (dsts *FindDepositsToSweepTestScenario) UnmarshalJSON(data []byte) error { RevealBlockNumber uint64 SweptAt int64 } - ExpectedWalletPublicKeyHash string - ExpectedUnsweptDeposits []struct { + ExpectedUnsweptDeposits []struct { FundingTxHash string FundingOutputIndex uint32 RevealBlockNumber uint64 @@ -71,22 +65,13 @@ func (dsts *FindDepositsToSweepTestScenario) UnmarshalJSON(data []byte) error { // Unmarshal title. dsts.Title = unmarshaled.Title - // Unmarshal max number of deposits. + // Unmarshal wallet PKH. if len(unmarshaled.WalletPublicKeyHash) > 0 { copy(dsts.WalletPublicKeyHash[:], hexToSlice(unmarshaled.WalletPublicKeyHash)) } dsts.MaxNumberOfDeposits = unmarshaled.MaxNumberOfDeposits - // Unmarshal wallets. - for _, uw := range unmarshaled.Wallets { - w := new(Wallet) - - copy(w.WalletPublicKeyHash[:], hexToSlice(uw.WalletPublicKeyHash)) - w.RegistrationBlockNumber = uw.RegistrationBlockNumber - - dsts.Wallets = append(dsts.Wallets, w) - } // Unmarshal deposits. for i, deposit := range unmarshaled.Deposits { d := new(Deposit) @@ -113,11 +98,6 @@ func (dsts *FindDepositsToSweepTestScenario) UnmarshalJSON(data []byte) error { dsts.Deposits = append(dsts.Deposits, d) } - // Unmarshal expected wallet public key hash. - if len(unmarshaled.ExpectedWalletPublicKeyHash) > 0 { - copy(dsts.ExpectedWalletPublicKeyHash[:], hexToSlice(unmarshaled.ExpectedWalletPublicKeyHash)) - } - // Unmarshal expected unswept deposits. for i, deposit := range unmarshaled.ExpectedUnsweptDeposits { ud := new(tbtcpg.DepositReference) @@ -284,23 +264,15 @@ func (fprts *FindPendingRedemptionsTestScenario) UnmarshalJSON(data []byte) erro RequestTimeout uint32 RequestMinAge uint32 } - Filter struct { - WalletPublicKeyHashes []string - WalletsLimit uint16 - RequestsLimit uint16 - RequestAmountLimit uint64 - } - Wallets []struct { - WalletPublicKeyHash string - RegistrationBlockNumber uint64 - } - PendingRedemptions []struct { + WalletPublicKeyHash string + MaxNumberOfRequests uint16 + PendingRedemptions []struct { WalletPublicKeyHash string RedeemerOutputScript string RequestedAmount uint64 Age int64 } - ExpectedWalletsPendingRedemptions map[string][]string + ExpectedRedeemersOutputScripts []string } var unmarshaled findPendingRedemptionsTestScenario @@ -318,29 +290,12 @@ func (fprts *FindPendingRedemptionsTestScenario) UnmarshalJSON(data []byte) erro fprts.ChainParameters.RequestTimeout = unmarshaled.ChainParameters.RequestTimeout fprts.ChainParameters.RequestMinAge = unmarshaled.ChainParameters.RequestMinAge - for _, wpkhString := range unmarshaled.Filter.WalletPublicKeyHashes { - var wpkh [20]byte - copy(wpkh[:], hexToSlice(wpkhString)) - - fprts.Filter.WalletPublicKeyHashes = append( - fprts.Filter.WalletPublicKeyHashes, - wpkh, - ) + // Unmarshal wallet PKH. + if len(unmarshaled.WalletPublicKeyHash) > 0 { + copy(fprts.WalletPublicKeyHash[:], hexToSlice(unmarshaled.WalletPublicKeyHash)) } - fprts.Filter.WalletsLimit = unmarshaled.Filter.WalletsLimit - fprts.Filter.RequestsLimit = unmarshaled.Filter.RequestsLimit - fprts.Filter.RequestAmountLimit = unmarshaled.Filter.RequestAmountLimit - - for _, w := range unmarshaled.Wallets { - var wpkh [20]byte - copy(wpkh[:], hexToSlice(w.WalletPublicKeyHash)) - - fprts.Wallets = append(fprts.Wallets, &Wallet{ - WalletPublicKeyHash: wpkh, - RegistrationBlockNumber: w.RegistrationBlockNumber, - }) - } + fprts.MaxNumberOfRequests = unmarshaled.MaxNumberOfRequests now := time.Now() currentBlock := fprts.ChainParameters.CurrentBlock @@ -368,17 +323,12 @@ func (fprts *FindPendingRedemptionsTestScenario) UnmarshalJSON(data []byte) erro ) } - fprts.ExpectedWalletsPendingRedemptions = make(map[[20]byte][]bitcoin.Script) - for wpkhString, scripts := range unmarshaled.ExpectedWalletsPendingRedemptions { - var wpkh [20]byte - copy(wpkh[:], hexToSlice(wpkhString)) - - convertedScripts := make([]bitcoin.Script, len(scripts)) - for i, script := range scripts { - convertedScripts[i] = hexToSlice(script) - } - - fprts.ExpectedWalletsPendingRedemptions[wpkh] = convertedScripts + fprts.ExpectedRedeemersOutputScripts = make([]bitcoin.Script, 0) + for _, s := range unmarshaled.ExpectedRedeemersOutputScripts { + fprts.ExpectedRedeemersOutputScripts = append( + fprts.ExpectedRedeemersOutputScripts, + hexToSlice(s), + ) } return nil diff --git a/pkg/tbtcpg/internal/test/tbtcpgtest.go b/pkg/tbtcpg/internal/test/tbtcpgtest.go index b27dde21dd..7245b3d181 100644 --- a/pkg/tbtcpg/internal/test/tbtcpgtest.go +++ b/pkg/tbtcpg/internal/test/tbtcpgtest.go @@ -3,6 +3,7 @@ package test import ( "encoding/json" "fmt" + "github.com/keep-network/keep-core/pkg/tbtcpg" "io/fs" "io/ioutil" "path/filepath" @@ -10,8 +11,6 @@ import ( "strings" "time" - "github.com/keep-network/keep-core/pkg/tbtcpg" - "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" ) @@ -23,12 +22,6 @@ const ( findPendingRedemptionsTestDataFilePrefix = "find_pending_redemptions" ) -// Wallet holds the wallet data in the given test scenario. -type Wallet struct { - WalletPublicKeyHash [20]byte - RegistrationBlockNumber uint64 -} - // Deposit holds the deposit data in the given test scenario. type Deposit struct { FundingTxHash bitcoin.Hash @@ -49,11 +42,9 @@ type FindDepositsToSweepTestScenario struct { MaxNumberOfDeposits uint16 WalletPublicKeyHash [20]byte - Wallets []*Wallet Deposits []*Deposit - ExpectedWalletPublicKeyHash [20]byte - ExpectedUnsweptDeposits []*tbtcpg.DepositReference + ExpectedUnsweptDeposits []*tbtcpg.DepositReference SweepTxFee int64 EstimateSatPerVByteFee int64 @@ -114,17 +105,21 @@ type RedemptionRequest struct { // FindPendingRedemptionsTestScenario represents a test scenario of finding // pending redemptions. type FindPendingRedemptionsTestScenario struct { - Title string + Title string + ChainParameters struct { AverageBlockTime time.Duration CurrentBlock uint64 RequestTimeout uint32 RequestMinAge uint32 } - Filter tbtcpg.PendingRedemptionsFilter - Wallets []*Wallet - PendingRedemptions []*RedemptionRequest - ExpectedWalletsPendingRedemptions map[[20]byte][]bitcoin.Script + + MaxNumberOfRequests uint16 + WalletPublicKeyHash [20]byte + + PendingRedemptions []*RedemptionRequest + + ExpectedRedeemersOutputScripts []bitcoin.Script } // LoadFindPendingRedemptionsTestScenario loads all scenarios related with diff --git a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json index 9d03a5674e..6af9703084 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json +++ b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_0.json @@ -1,21 +1,7 @@ { - "Title": "Auto-select wallet and filter out swept deposits", - "MaxNumberOfDeposits": 3, - "WalletPublicKeyHash": "", - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], + "Title": "unswept and confirmed deposits exist", + "MaxNumberOfDeposits": 5, + "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", "Deposits": [ { "RevealBlockNumber": 6, @@ -26,36 +12,18 @@ "FundingTxHex": "02000000000101118b50a9fe2ac54cc581d11f87217cfaacd291f49bb07fc121acf2528f942bb20100000017160014ba88d77b2b41f7de4186d0ea01952ba59ab05d2cfdffffff02ed8f570b0000000017a914e5194b41a0b5105ec1abdbc324045bacde00932b87804f120000000000220020cdbdbc6f38c6218c1ad8fdae8605c42bcfe586fdf48d18136d72dc61eadf3417024730440220622f94dc31bffd12069d6386775d58a8d83b4c52e4223d9c8001bc1751cdec8302203ecee6b8d053c7a583614cbfb1262a80bb353bd486124f8f0356e0281aed85f701210248679793ff33704b185bbb06487f61b32c081ea605fe4a8a33e5271f30c8269100000000", "SweptAt": 102 }, - { - "RevealBlockNumber": 7, - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "FundingTxHash": "8356227b87fb191d61cff2455cd3f8c33ae208c69a7698b5baf1a331db6d4d8f", - "FundingOutputIndex": 2, - "FundingTxConfirmations": 34, - "FundingTxHex": "02000000000101798b177a7b8e62494299a5823c389ee88d687d2d2248ad49cdbcec072bbabcef0100000023220020406368b7e3c4861326b216091f3700402a3cce4326c5975f9b5407c7544662bbfdffffff022f1e4b160000000017a914250760145b801de5c7787a35a3d0bd4dae129f7c8740420f0000000000220020663373ae8887c16fb3f0f312f9c0306f2c7b48eb11fade901baa4fa9f7f8b4bb0347304402203accf377fbd65c6cd223147416732b7c32c5502f081372dfcfb085937eac68790220106bd48535db08ed325b12e59006300a5f7d45efc0fb5e0ba9bd052eb9db6ec20147304402200bffe4bc7a743f2f9c9745b81f7303f9c86a57df04fa04b34adb7cf3dc630c5702206aded842db1b8a710d8113d5317d12b7a20fc2dd9ddfd0537dab8fd26bae2a19014e210288e71799f2480443b10147d37e4f41976b6c2c220ea89e5053ed2a58333a9687ad210211fc7914551272419d73ec2e873050afa3f37732fd4fc891d211149d66bf1ac6ac73640380ca00b268cce52400", - "SweptAt": 0 - }, { "RevealBlockNumber": 11, "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "FundingTxHash": "d91868ca43db4deb96047d727a5e782f282864fde2d9364f8c562c8998ba64bf", "FundingOutputIndex": 1, - "FundingTxConfirmations": 6, + "FundingTxConfirmations": 20, "FundingTxHex": "0200000000010180c16723b30e6bd2ed340b1d5c9c581765591daed800e16b7970c160aba89b1600000000232200204c469cd67de0cfde55e8067570178dc1935a8387f8b4005b5127918fb08da636fdffffff02854571160000000017a914e26de2c3f4ebaedabe94ca3f5374edcb5c2010af8793ec1700000000002200205fc0c323555ad54bfbc0f2d88429fb660208248b2711e22c3dde3f340e5a94e4034730440220694178395f9c4b1b2a7ebd450797d2aa23ec1082c4b7a8ccab018e675d64c71f02200a5cf90497c16b2888ae48087d59bf52e3501ea7229d77db47d8d761a006804b014730440220550ed7a454a8f7c26dcdb5ff887a21baa7f4c9de140eda0ee8e83760e839f29e02200902c3ed2fc1acb0c10f28289914ca9d027dfcdbf3469e3594e7c1b421e26642014e2103e749c7066765961417a5f714e6bc3075b9c517f4743e211d308a5d1bbd39c531ad21025a7941db7bf0f6345125470e7bf70fff8ad78bd5d1ac26b689ae554351afa14cac73640380ca00b2686be52400", "SweptAt": 0 }, { "RevealBlockNumber": 12, "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "FundingTxHash": "22703dae51de3c45ded183a39a345c101e108df32d50a1d9282e95dee67b9cfd", - "FundingOutputIndex": 0, - "FundingTxConfirmations": 5, - "FundingTxHex": "0200000000010236e005bedbfe340d73f282debf5f28b436b72ed06d9b8b1d3b71da6992158d810100000023220020e33c1bef27da93c100a0626a9dff06059d088b091b038f3b54510e3667fe5775fdffffff0e426895ab4c5b9aa09e063366d5f7668ed912d4d99809d70ac24faeb65bd6a6000000002322002064d1e5adca22a6bf68b4db5d11d6cd0eeabca62efbcfa7346be57a1cf3b4b6affdffffff0240420f000000000022002058e34e684a5d823c40f069fb2235f156741c3e7c675a47fdcacf5718d517763aa4cee0050000000017a914e759f47b61cae21f9478aaae06395f8a7dce22aa8703473044022028d3104e19a40717f854c16a31fc41224abedf6fed87996dacf193561193b5a002204e80434e6609c4b0c8112c2603e717c98b75f676de9c40c628d210d9bf28ed3a014730440220774d6049dec6c3962fed40b6074b6757c5979ebfe9bafe3c6150e17adbbef4c70220672fc08eb4cfeafa021ab15119d46a95ce4ce607d1f65dd53e9ded0e53216012014e2103b4487135c395b553e0b344073dcc0005f49212fe5fc9c42761d24e0cc49caf90ad210358d8e8275e8e038194c4f508d41debfe8e87303427222e37c73254c2a329d809ac73640380ca00b2680347304402205ffa05c267dc891ab5012516ce13f7f3021e7826c8e4c5ae57cedf5cea0283c902202444822c9f43cc977154560553ba74166d1b5de9622f4481afd92b9f70e84cc8014730440220593a13011104596c57bdb9794d169ff7950884baadf51d34b42ecb84f5104bb602203b5d49897ac2cdb8f25afbc4cd18b99f788a8f19f009e4edc97a76c40f3a9173014e21030ad8a268049e4010b52c4debb7afa36260c8641f96527b5e18b23c7ad702f42cad210236e934c5ab487b83678cfbc0b3c07fbdac5dec5beb8ddcf959d7a5528ba8a999ac73640380ca00b26838e62400", - "SweptAt": 0 - }, - { - "RevealBlockNumber": 13, - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "FundingTxHash": "c693aaa540d22bd7cf70c17cb71c1d789ea68138d87fdb6ea70e450effeb2137", "FundingOutputIndex": 0, "FundingTxConfirmations": 23, @@ -75,7 +43,7 @@ "RevealBlockNumber": 22, "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", "FundingTxHash": "178628f0fd77ca04a31e771805277405288404da38eb86a0e369f2b26d45fe97", - "FundingOutputIndex": 0, + "FundingOutputIndex": 1, "FundingTxConfirmations": 34, "FundingTxHex": "02000000000101927dcff5fa228252124c24e4746141fe0db9fc41f4712d449fad9f33fe3fa3780000000023220020481719bc4e385205ed350423b31ecd15519897a40abd9996ffd4abe31dd57cb9fdffffff02e5a939080000000017a914239b741cd84ba7eadc838839288f1cfbbb218a248740420f00000000002200200c273a194459d101f66ed8fd231b4188726e1b86b7bd80cde376231779522d540347304402201f8befe88ee75d96a05b27635f5a6f178f79e33efd8323e948c43c7f8d84bf7802205e7c642b1dfbb0e584c823e375294520b800d8cd76f6ee717cb8044f9156b19801473044022005373bbe8fc4fa09fc918cb75c20fa69ece69ba5377c80c89fd08781d2d2ecf20220071e1cdd5a8b6cfcba9118755bcaac923d94a967887c9ffbc5653559fab73877014e21039974c5b0c669201c6e3e4715089c710291d208c584e1fe77ae44a9d81f065371ad21021df7f70250ff11cda7363b91f6c4615b4063c43331ab1b0d61d7e2763833efebac73640380ca00b268b6e62400", "SweptAt": 109 @@ -91,29 +59,18 @@ }, { "RevealBlockNumber": 32, - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", + "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", "FundingTxHash": "b822b302dab7c1fcc3292782635be133538a0f803468a2d847023c24f867f479", - "FundingOutputIndex": 1, - "FundingTxConfirmations": 25, + "FundingOutputIndex": 3, + "FundingTxConfirmations": 5, "FundingTxHex": "02000000000101d9eae6dedef56edcc4c884a84f4127c92a18f1674562960ed7770656165d32b101000000232200209fd1e4519124fe6655ba29f9690fc00a03a3d3b491db3b7f710614b8d8024d8dfdffffff0240420f0000000000220020e99a81c3fa4e98410937231296f33538b03c614c1eb8de6a51f03e1b404d0b19ccb566050000000017a9146323da8694056713624b4818d4ccc2433dd7971d8703473044022063ff0a0bbee811b393f7badc091bffc913c7dbc921a3bbcd4c5c873a946f353b022074c128f86c7a4b826ece9b15c612cacbd2ab064c9b4bf27671f545069b803f750147304402200d1d56c74355d68d6a3f59b3366ed3e3254da16e045b44bd1ac6e64059c7439c0220741c03eeffb437d6849730e9eef9c063b0e74dd3fdac54c06853992c0718bea5014e2103897c250217abd55d3a332788f08b7682a44137b5c9d4f882f985059d9c1c3821ad21034ced46df0f98b8cbf2aa8803cffd60a5b475f0745d0f7d9369aa88e122f9a313ac73640380ca00b268df132500", "SweptAt": 0 } ], - "ExpectedWalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "ExpectedUnsweptDeposits": [ { - "RevealBlockNumber": 11, - "FundingTxHash": "d91868ca43db4deb96047d727a5e782f282864fde2d9364f8c562c8998ba64bf", - "FundingOutputIndex": 1 - }, - { - "RevealBlockNumber": 31, - "FundingTxHash": "a3d1781b59d5e8680772a8bb7f897c4ff0459d3465d7fa678f80a4f0ec900574", - "FundingOutputIndex": 0 - }, - { - "RevealBlockNumber": 32, - "FundingTxHash": "b822b302dab7c1fcc3292782635be133538a0f803468a2d847023c24f867f479", + "RevealBlockNumber": 21, + "FundingTxHash": "a8c3b3c1975094550d481bdffdee1b7b7613dd74dbce37a5f6dce7fd9ac0ace1", "FundingOutputIndex": 1 } ] diff --git a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json index 4ed79ef5aa..1fe1ce556d 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json +++ b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_1.json @@ -1,21 +1,7 @@ { - "Title": "Get deposits from a selected wallet", + "Title": "unswept and confirmed deposits do not exist", "MaxNumberOfDeposits": 5, - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "Deposits": [ { "RevealBlockNumber": 6, @@ -57,7 +43,7 @@ "RevealBlockNumber": 22, "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", "FundingTxHash": "178628f0fd77ca04a31e771805277405288404da38eb86a0e369f2b26d45fe97", - "FundingOutputIndex": 1, + "FundingOutputIndex": 4, "FundingTxConfirmations": 34, "FundingTxHex": "02000000000101927dcff5fa228252124c24e4746141fe0db9fc41f4712d449fad9f33fe3fa3780000000023220020481719bc4e385205ed350423b31ecd15519897a40abd9996ffd4abe31dd57cb9fdffffff02e5a939080000000017a914239b741cd84ba7eadc838839288f1cfbbb218a248740420f00000000002200200c273a194459d101f66ed8fd231b4188726e1b86b7bd80cde376231779522d540347304402201f8befe88ee75d96a05b27635f5a6f178f79e33efd8323e948c43c7f8d84bf7802205e7c642b1dfbb0e584c823e375294520b800d8cd76f6ee717cb8044f9156b19801473044022005373bbe8fc4fa09fc918cb75c20fa69ece69ba5377c80c89fd08781d2d2ecf20220071e1cdd5a8b6cfcba9118755bcaac923d94a967887c9ffbc5653559fab73877014e21039974c5b0c669201c6e3e4715089c710291d208c584e1fe77ae44a9d81f065371ad21021df7f70250ff11cda7363b91f6c4615b4063c43331ab1b0d61d7e2763833efebac73640380ca00b268b6e62400", "SweptAt": 109 @@ -73,20 +59,13 @@ }, { "RevealBlockNumber": 32, - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "FundingTxHash": "b822b302dab7c1fcc3292782635be133538a0f803468a2d847023c24f867f479", "FundingOutputIndex": 3, - "FundingTxConfirmations": 25, + "FundingTxConfirmations": 5, "FundingTxHex": "02000000000101d9eae6dedef56edcc4c884a84f4127c92a18f1674562960ed7770656165d32b101000000232200209fd1e4519124fe6655ba29f9690fc00a03a3d3b491db3b7f710614b8d8024d8dfdffffff0240420f0000000000220020e99a81c3fa4e98410937231296f33538b03c614c1eb8de6a51f03e1b404d0b19ccb566050000000017a9146323da8694056713624b4818d4ccc2433dd7971d8703473044022063ff0a0bbee811b393f7badc091bffc913c7dbc921a3bbcd4c5c873a946f353b022074c128f86c7a4b826ece9b15c612cacbd2ab064c9b4bf27671f545069b803f750147304402200d1d56c74355d68d6a3f59b3366ed3e3254da16e045b44bd1ac6e64059c7439c0220741c03eeffb437d6849730e9eef9c063b0e74dd3fdac54c06853992c0718bea5014e2103897c250217abd55d3a332788f08b7682a44137b5c9d4f882f985059d9c1c3821ad21034ced46df0f98b8cbf2aa8803cffd60a5b475f0745d0f7d9369aa88e122f9a313ac73640380ca00b268df132500", "SweptAt": 0 } ], - "ExpectedWalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "ExpectedUnsweptDeposits": [ - { - "RevealBlockNumber": 21, - "FundingTxHash": "a8c3b3c1975094550d481bdffdee1b7b7613dd74dbce37a5f6dce7fd9ac0ace1", - "FundingOutputIndex": 1 - } - ] + "ExpectedUnsweptDeposits": [] } diff --git a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json index 7ae143fffc..31c61d730a 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json +++ b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_2.json @@ -1,21 +1,7 @@ { - "Title": "Get deposits from a wallet that has only swept deposits", - "MaxNumberOfDeposits": 5, + "Title": "unswept and confirmed deposits exist but exceed max number", + "MaxNumberOfDeposits": 2, "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], "Deposits": [ { "RevealBlockNumber": 6, @@ -24,7 +10,7 @@ "FundingOutputIndex": 1, "FundingTxConfirmations": 20, "FundingTxHex": "02000000000101118b50a9fe2ac54cc581d11f87217cfaacd291f49bb07fc121acf2528f942bb20100000017160014ba88d77b2b41f7de4186d0ea01952ba59ab05d2cfdffffff02ed8f570b0000000017a914e5194b41a0b5105ec1abdbc324045bacde00932b87804f120000000000220020cdbdbc6f38c6218c1ad8fdae8605c42bcfe586fdf48d18136d72dc61eadf3417024730440220622f94dc31bffd12069d6386775d58a8d83b4c52e4223d9c8001bc1751cdec8302203ecee6b8d053c7a583614cbfb1262a80bb353bd486124f8f0356e0281aed85f701210248679793ff33704b185bbb06487f61b32c081ea605fe4a8a33e5271f30c8269100000000", - "SweptAt": 102 + "SweptAt": 0 }, { "RevealBlockNumber": 11, @@ -64,7 +50,7 @@ }, { "RevealBlockNumber": 31, - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "FundingTxHash": "a3d1781b59d5e8680772a8bb7f897c4ff0459d3465d7fa678f80a4f0ec900574", "FundingOutputIndex": 0, "FundingTxConfirmations": 23, @@ -73,7 +59,7 @@ }, { "RevealBlockNumber": 32, - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "FundingTxHash": "b822b302dab7c1fcc3292782635be133538a0f803468a2d847023c24f867f479", "FundingOutputIndex": 3, "FundingTxConfirmations": 25, @@ -81,6 +67,16 @@ "SweptAt": 0 } ], - "ExpectedWalletPublicKeyHash": "", - "ExpectedUnsweptDeposits": [] + "ExpectedUnsweptDeposits": [ + { + "RevealBlockNumber": 6, + "FundingTxHash": "dbc6166e149995ef4ed6428d23703756971256ab19954e099fdf2a96fa332251", + "FundingOutputIndex": 1 + }, + { + "RevealBlockNumber": 31, + "FundingTxHash": "a3d1781b59d5e8680772a8bb7f897c4ff0459d3465d7fa678f80a4f0ec900574", + "FundingOutputIndex": 0 + } + ] } diff --git a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json b/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json deleted file mode 100644 index c0dc1b480f..0000000000 --- a/pkg/tbtcpg/internal/test/testdata/find_deposits_scenario_3.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "Title": "Auto-select wallet when there is just one wallet", - "MaxNumberOfDeposits": 3, - "WalletPublicKeyHash": "", - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - } - ], - "Deposits": [ - { - "RevealBlockNumber": 6, - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "FundingTxHash": "dbc6166e149995ef4ed6428d23703756971256ab19954e099fdf2a96fa332251", - "FundingOutputIndex": 1, - "FundingTxConfirmations": 20, - "FundingTxHex": "02000000000101118b50a9fe2ac54cc581d11f87217cfaacd291f49bb07fc121acf2528f942bb20100000017160014ba88d77b2b41f7de4186d0ea01952ba59ab05d2cfdffffff02ed8f570b0000000017a914e5194b41a0b5105ec1abdbc324045bacde00932b87804f120000000000220020cdbdbc6f38c6218c1ad8fdae8605c42bcfe586fdf48d18136d72dc61eadf3417024730440220622f94dc31bffd12069d6386775d58a8d83b4c52e4223d9c8001bc1751cdec8302203ecee6b8d053c7a583614cbfb1262a80bb353bd486124f8f0356e0281aed85f701210248679793ff33704b185bbb06487f61b32c081ea605fe4a8a33e5271f30c8269100000000", - "SweptAt": 102 - }, - { - "RevealBlockNumber": 7, - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "FundingTxHash": "8356227b87fb191d61cff2455cd3f8c33ae208c69a7698b5baf1a331db6d4d8f", - "FundingOutputIndex": 1, - "FundingTxConfirmations": 34, - "FundingTxHex": "02000000000101798b177a7b8e62494299a5823c389ee88d687d2d2248ad49cdbcec072bbabcef0100000023220020406368b7e3c4861326b216091f3700402a3cce4326c5975f9b5407c7544662bbfdffffff022f1e4b160000000017a914250760145b801de5c7787a35a3d0bd4dae129f7c8740420f0000000000220020663373ae8887c16fb3f0f312f9c0306f2c7b48eb11fade901baa4fa9f7f8b4bb0347304402203accf377fbd65c6cd223147416732b7c32c5502f081372dfcfb085937eac68790220106bd48535db08ed325b12e59006300a5f7d45efc0fb5e0ba9bd052eb9db6ec20147304402200bffe4bc7a743f2f9c9745b81f7303f9c86a57df04fa04b34adb7cf3dc630c5702206aded842db1b8a710d8113d5317d12b7a20fc2dd9ddfd0537dab8fd26bae2a19014e210288e71799f2480443b10147d37e4f41976b6c2c220ea89e5053ed2a58333a9687ad210211fc7914551272419d73ec2e873050afa3f37732fd4fc891d211149d66bf1ac6ac73640380ca00b268cce52400", - "SweptAt": 0 - } - ], - "ExpectedWalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "ExpectedUnsweptDeposits": [ - { - "RevealBlockNumber": 7, - "FundingTxHash": "8356227b87fb191d61cff2455cd3f8c33ae208c69a7698b5baf1a331db6d4d8f", - "FundingOutputIndex": 1 - } - ] -} diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json index 09ea647f9b..b7eab4a4c4 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json +++ b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_0.json @@ -1,31 +1,13 @@ { - "Title": "Get all pending redemptions without filtering", + "Title": "pending redemptions exist", "ChainParameters":{ "AverageBlockTime": 10, "CurrentBlock": 100000, "RequestTimeout": 86400, "RequestMinAge": 3600 }, - "Filter": { - "WalletPublicKeyHashes": [], - "WalletsLimit": 0, - "RequestsLimit": 0, - "RequestAmountLimit": 0 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], + "MaxNumberOfRequests": 5, + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "PendingRedemptions": [ { "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", @@ -64,18 +46,8 @@ "Age": 10800 } ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000001", - "0x00140000000000000000000000000000000000000002" - ], - "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e": [ - "0x00140000000000000000000000000000000000000003", - "0x00140000000000000000000000000000000000000004" - ], - "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed": [ - "0x00140000000000000000000000000000000000000005", - "0x00140000000000000000000000000000000000000006" - ] - } + "ExpectedRedeemersOutputScripts": [ + "0x00140000000000000000000000000000000000000001", + "0x00140000000000000000000000000000000000000002" + ] } diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json index d35ae0fe09..a1af455e9d 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json +++ b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_1.json @@ -1,34 +1,13 @@ { - "Title": "Get pending redemptions for specific wallets", + "Title": "pending redemptions exist but exceed max number", "ChainParameters":{ "AverageBlockTime": 10, "CurrentBlock": 100000, "RequestTimeout": 86400, "RequestMinAge": 3600 }, - "Filter": { - "WalletPublicKeyHashes": [ - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed" - ], - "WalletsLimit": 0, - "RequestsLimit": 0, - "RequestAmountLimit": 0 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], + "MaxNumberOfRequests": 1, + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "PendingRedemptions": [ { "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", @@ -67,14 +46,7 @@ "Age": 10800 } ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000001", - "0x00140000000000000000000000000000000000000002" - ], - "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed": [ - "0x00140000000000000000000000000000000000000005", - "0x00140000000000000000000000000000000000000006" - ] - } + "ExpectedRedeemersOutputScripts": [ + "0x00140000000000000000000000000000000000000001" + ] } diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json index 588efe6bb2..6089f2cf4b 100644 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json +++ b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_2.json @@ -1,43 +1,25 @@ { - "Title": "Get pending redemptions with wallets count limit", + "Title": "pending redemptions do not exist", "ChainParameters":{ "AverageBlockTime": 10, "CurrentBlock": 100000, "RequestTimeout": 86400, "RequestMinAge": 3600 }, - "Filter": { - "WalletPublicKeyHashes": [], - "WalletsLimit": 1, - "RequestsLimit": 0, - "RequestAmountLimit": 0 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], + "MaxNumberOfRequests": 5, + "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "PendingRedemptions": [ { "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "RedeemerOutputScript": "0x00140000000000000000000000000000000000000001", "RequestedAmount": 1000000000, - "Age": 79200 + "Age": 90000 }, { "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", "RedeemerOutputScript": "0x00140000000000000000000000000000000000000002", "RequestedAmount": 2000000000, - "Age": 75600 + "Age": 100000 }, { "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", @@ -61,13 +43,8 @@ "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", "RedeemerOutputScript": "0x00140000000000000000000000000000000000000006", "RequestedAmount": 6000000000, - "Age": 10800 + "Age": 600 } ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000001", - "0x00140000000000000000000000000000000000000002" - ] - } + "ExpectedRedeemersOutputScripts": [] } diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json deleted file mode 100644 index eb28d6b861..0000000000 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_3.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "Title": "Get pending redemptions with requests count limit", - "ChainParameters":{ - "AverageBlockTime": 10, - "CurrentBlock": 100000, - "RequestTimeout": 86400, - "RequestMinAge": 3600 - }, - "Filter": { - "WalletPublicKeyHashes": [], - "WalletsLimit": 0, - "RequestsLimit": 1, - "RequestAmountLimit": 0 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], - "PendingRedemptions": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000001", - "RequestedAmount": 1000000000, - "Age": 79200 - }, - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000002", - "RequestedAmount": 2000000000, - "Age": 75600 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000003", - "RequestedAmount": 3000000000, - "Age": 36000 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000004", - "RequestedAmount": 4000000000, - "Age": 32400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000005", - "RequestedAmount": 5000000000, - "Age": 14400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000006", - "RequestedAmount": 6000000000, - "Age": 10800 - } - ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000001" - ], - "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e": [ - "0x00140000000000000000000000000000000000000003" - ], - "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed": [ - "0x00140000000000000000000000000000000000000005" - ] - } -} diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json deleted file mode 100644 index 31fa86e47b..0000000000 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_4.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "Title": "Get pending redemptions with requests amount limit", - "ChainParameters":{ - "AverageBlockTime": 10, - "CurrentBlock": 100000, - "RequestTimeout": 86400, - "RequestMinAge": 3600 - }, - "Filter": { - "WalletPublicKeyHashes": [], - "WalletsLimit": 0, - "RequestsLimit": 0, - "RequestAmountLimit": 4000000000 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], - "PendingRedemptions": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000001", - "RequestedAmount": 1000000000, - "Age": 79200 - }, - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000002", - "RequestedAmount": 2000000000, - "Age": 75600 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000003", - "RequestedAmount": 3000000000, - "Age": 36000 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000004", - "RequestedAmount": 4000000000, - "Age": 32400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000005", - "RequestedAmount": 5000000000, - "Age": 14400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000006", - "RequestedAmount": 6000000000, - "Age": 10800 - } - ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000001", - "0x00140000000000000000000000000000000000000002" - ], - "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e": [ - "0x00140000000000000000000000000000000000000003", - "0x00140000000000000000000000000000000000000004" - ] - } -} diff --git a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json b/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json deleted file mode 100644 index 61b2a19d5c..0000000000 --- a/pkg/tbtcpg/internal/test/testdata/find_pending_redemptions_scenario_5.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "Title": "Get pending redemptions where there are too old and too young ones", - "ChainParameters":{ - "AverageBlockTime": 10, - "CurrentBlock": 100000, - "RequestTimeout": 86400, - "RequestMinAge": 3600 - }, - "Filter": { - "WalletPublicKeyHashes": [], - "WalletsLimit": 0, - "RequestsLimit": 0, - "RequestAmountLimit": 0 - }, - "Wallets": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RegistrationBlockNumber": 5 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RegistrationBlockNumber": 10 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RegistrationBlockNumber": 20 - } - ], - "PendingRedemptions": [ - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000001", - "RequestedAmount": 1000000000, - "Age": 90000 - }, - { - "WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000002", - "RequestedAmount": 2000000000, - "Age": 75600 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000003", - "RequestedAmount": 3000000000, - "Age": 36000 - }, - { - "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000004", - "RequestedAmount": 4000000000, - "Age": 32400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000005", - "RequestedAmount": 5000000000, - "Age": 14400 - }, - { - "WalletPublicKeyHash": "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed", - "RedeemerOutputScript": "0x00140000000000000000000000000000000000000006", - "RequestedAmount": 6000000000, - "Age": 600 - } - ], - "ExpectedWalletsPendingRedemptions": { - "0x928d992e5f5b71de51a1b40fcc4056b99a88a647": [ - "0x00140000000000000000000000000000000000000002" - ], - "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e": [ - "0x00140000000000000000000000000000000000000003", - "0x00140000000000000000000000000000000000000004" - ], - "0x7670343fc00ccc2d0cd65360e6ad400697ea0fed": [ - "0x00140000000000000000000000000000000000000005" - ] - } -} diff --git a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json index bd6c7a2ce3..500b4afc62 100644 --- a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json +++ b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_0.json @@ -1,5 +1,5 @@ { - "Title": "Estimate sweep transaction fee", + "Title": "estimate sweep transaction fee", "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "DepositTxMaxFee": 10000, "Deposits": [ diff --git a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json index 706e25b31a..c46cfb6512 100644 --- a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json +++ b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_1.json @@ -1,5 +1,5 @@ { - "Title": "Sweep transaction fee provided", + "Title": "sweep transaction fee provided", "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "DepositTxMaxFee": 10000, "Deposits": [ diff --git a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json index 6b58aa6a03..3922cade6a 100644 --- a/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json +++ b/pkg/tbtcpg/internal/test/testdata/propose_sweep_scenario_2.json @@ -1,5 +1,5 @@ { - "Title": "Estimated sweep transaction fee greater than max fee", + "Title": "estimated sweep transaction fee greater than max fee", "WalletPublicKeyHash": "0x03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e", "DepositTxMaxFee": 301, "Deposits": [ diff --git a/pkg/tbtcpg/redemptions.go b/pkg/tbtcpg/redemptions.go index 8478778055..54fc6aac1e 100644 --- a/pkg/tbtcpg/redemptions.go +++ b/pkg/tbtcpg/redemptions.go @@ -1,11 +1,11 @@ package tbtcpg import ( - "encoding/hex" "fmt" + "github.com/ipfs/go-log/v2" + "go.uber.org/zap" "math/big" "sort" - "strings" "time" "github.com/keep-network/keep-core/internal/hexutils" @@ -13,27 +13,32 @@ import ( "github.com/keep-network/keep-core/pkg/tbtc" ) -// redemptionTask is a task that may produce a redemption proposal. -type redemptionTask struct { +// RedemptionTask is a task that may produce a redemption proposal. +type RedemptionTask struct { chain Chain btcChain bitcoin.Chain } -func newRedemptionTask( +func NewRedemptionTask( chain Chain, btcChain bitcoin.Chain, -) *redemptionTask { - return &redemptionTask{ +) *RedemptionTask { + return &RedemptionTask{ chain: chain, btcChain: btcChain, } } -func (rt *redemptionTask) run(walletPublicKeyHash [20]byte) ( +func (rt *RedemptionTask) Run(walletPublicKeyHash [20]byte) ( tbtc.CoordinationProposal, bool, error, ) { + taskLogger := logger.With( + zap.String("task", rt.ActionType().String()), + zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), + ) + redemptionMaxSize, err := rt.chain.GetRedemptionMaxSize() if err != nil { return nil, false, fmt.Errorf( @@ -42,12 +47,10 @@ func (rt *redemptionTask) run(walletPublicKeyHash [20]byte) ( ) } - walletsPendingRedemptions, err := FindPendingRedemptions( - rt.chain, - PendingRedemptionsFilter{ - WalletPublicKeyHashes: [][20]byte{walletPublicKeyHash}, - RequestsLimit: redemptionMaxSize, - }, + redeemersOutputScripts, err := rt.FindPendingRedemptions( + taskLogger, + walletPublicKeyHash, + redemptionMaxSize, ) if err != nil { return nil, false, fmt.Errorf( @@ -56,18 +59,16 @@ func (rt *redemptionTask) run(walletPublicKeyHash [20]byte) ( ) } - redeemersOutputScripts, ok := walletsPendingRedemptions[walletPublicKeyHash] - if !ok { - logger.Info("no pending redemption requests") + if len(redeemersOutputScripts) == 0 { + taskLogger.Info("no pending redemption requests") return nil, false, nil } - proposal, err := ProposeRedemption( - rt.chain, - rt.btcChain, + proposal, err := rt.ProposeRedemption( + taskLogger, walletPublicKeyHash, - 0, redeemersOutputScripts, + 0, ) if err != nil { return nil, false, fmt.Errorf( @@ -79,7 +80,7 @@ func (rt *redemptionTask) run(walletPublicKeyHash [20]byte) ( return proposal, true, nil } -func (rt *redemptionTask) actionType() tbtc.WalletActionType { +func (rt *RedemptionTask) ActionType() tbtc.WalletActionType { return tbtc.ActionRedemption } @@ -92,59 +93,26 @@ type RedemptionRequest struct { RequestedAmount uint64 } -// PendingRedemptionsFilter defines some criteria that are used to filter -// pending redemption requests returned by the FindPendingRedemptions function. -type PendingRedemptionsFilter struct { - // WalletPublicKeyHashes limits the search space to specific wallets. - // The nil/empty value of this field means all wallets will be taken into - // account, starting from the oldest one. - WalletPublicKeyHashes [][20]byte - - // WalletsLimit limits the total number of wallets, starting from the - // oldest one. The value of 0 means there is no wallets limit. - WalletsLimit uint16 - - // RequestsLimit limits the number of redemptions requests per single wallet. - // The value of 0 means there is no requests limit per wallet. - RequestsLimit uint16 - - // RequestAmountLimit limits the search space to redemption requests - // whose requested amount does not exceed the given satoshi value. The - // value of 0 means there are no limit on the requested amount. - RequestAmountLimit uint64 -} - -func (prf PendingRedemptionsFilter) String() string { - wallets := make([]string, len(prf.WalletPublicKeyHashes)) - for i, wallet := range prf.WalletPublicKeyHashes { - wallets[i] = hex.EncodeToString(wallet[:]) - } - - return fmt.Sprintf( - "wallets: [%s], wallets limit: [%v], requests limit: [%v], "+ - "request amount limit: [%v]", - strings.Join(wallets, ", "), - prf.WalletsLimit, - prf.RequestsLimit, - prf.RequestAmountLimit, - ) -} - // FindPendingRedemptions finds pending redemptions requests according to // the provided filter. The returned value is a map, where the key is // a 20-byte public key hash of a specific wallet and the value is a list // of pending requests targeting this wallet. It is guaranteed that an existing // key has always a non-empty slice as value. -func FindPendingRedemptions( - chain Chain, - filter PendingRedemptionsFilter, -) (map[[20]byte][]bitcoin.Script, error) { - logger.Infof( - "looking for pending redemptions using filter [%s]", - filter, +func (rt *RedemptionTask) FindPendingRedemptions( + taskLogger log.StandardLogger, + walletPublicKeyHash [20]byte, + maxNumberOfRequests uint16, +) ([]bitcoin.Script, error) { + if walletPublicKeyHash == [20]byte{} { + return nil, fmt.Errorf("wallet public key hash is required") + } + + taskLogger.Infof( + "fetching max [%d] redemption requests", + maxNumberOfRequests, ) - blockCounter, err := chain.BlockCounter() + blockCounter, err := rt.chain.BlockCounter() if err != nil { return nil, fmt.Errorf( "failed to get block counter: [%w]", @@ -160,7 +128,7 @@ func FindPendingRedemptions( ) } - requestMinAge, err := chain.GetRedemptionRequestMinAge() + requestMinAge, err := rt.chain.GetRedemptionRequestMinAge() if err != nil { return nil, fmt.Errorf( "failed to get redemption request minimum age: [%w]", @@ -168,7 +136,7 @@ func FindPendingRedemptions( ) } - _, _, _, _, requestTimeout, _, _, err := chain.GetRedemptionParameters() + _, _, _, _, requestTimeout, _, _, err := rt.chain.GetRedemptionParameters() if err != nil { return nil, fmt.Errorf( "failed to get redemption parameters: [%w]", @@ -176,148 +144,60 @@ func FindPendingRedemptions( ) } - getPendingRedemptionsFromWallet := func( - wallet [20]byte, - ) ([]*RedemptionRequest, error) { - pendingRedemptions, err := getPendingRedemptions( - chain, - wallet, - currentBlockNumber, - filter.RequestsLimit, - requestTimeout, - requestMinAge, - filter.RequestAmountLimit, - ) - if err != nil { - return nil, - fmt.Errorf( - "failed to get pending redemptions for [%s] wallet: [%w]", - hexutils.Encode(wallet[:]), - err, - ) - } - return pendingRedemptions, nil - } - - var walletPublicKeyHashes [][20]byte - if len(filter.WalletPublicKeyHashes) > 0 { - // Take wallets from the filter. - walletPublicKeyHashes = filter.WalletPublicKeyHashes - } else { - // Take all wallets. - events, err := chain.PastNewWalletRegisteredEvents(nil) - if err != nil { - return nil, fmt.Errorf( - "failed to get new wallet registered wallets: [%w]", - err, - ) - } - - // Sort the wallets list from the oldest to the newest. - sort.SliceStable(events, func(i, j int) bool { - return events[i].BlockNumber < events[j].BlockNumber - }) - - for _, event := range events { - walletPublicKeyHashes = append( - walletPublicKeyHashes, - event.WalletPublicKeyHash, - ) - } - } + taskLogger.Infof("fetching pending redemption requests") - logger.Infof( - "built a list of [%v] wallets that will be checked "+ - "for pending redemption requests", - len(walletPublicKeyHashes), + pendingRedemptions, err := findPendingRedemptions( + taskLogger, + rt.chain, + walletPublicKeyHash, + currentBlockNumber, + maxNumberOfRequests, + requestTimeout, + requestMinAge, ) + if err != nil { + return nil, fmt.Errorf("cannot get pending redemptions: [%w]", err) + } - result := make(map[[20]byte][]bitcoin.Script) - - for _, walletPublicKeyHash := range walletPublicKeyHashes { - walletPublicKeyHashHex := hexutils.Encode(walletPublicKeyHash[:]) + taskLogger.Infof("found [%d] redemption requests", len(pendingRedemptions)) - logger.Infof( - "fetching pending redemption requests from wallet [%s]...", - walletPublicKeyHashHex, - ) + result := make([]bitcoin.Script, 0) - pendingRedemptions, err := getPendingRedemptionsFromWallet( - walletPublicKeyHash, + for _, pendingRedemption := range pendingRedemptions { + taskLogger.Infof( + "redemption request [%s] - requested at: [%s]", + pendingRedemption.RedemptionKey, + pendingRedemption.RequestedAt, ) - if err != nil { - return nil, fmt.Errorf( - "cannot get pending redemptions for wallet [%s]: [%w]", - walletPublicKeyHashHex, - err, - ) - } - - logger.Infof( - "found [%d] redemptions for wallet [%s]", - len(pendingRedemptions), - walletPublicKeyHashHex, - ) - - for i, pendingRedemption := range pendingRedemptions { - logger.Infof( - "redemption [%d/%d] - [%s]", - i+1, - len(pendingRedemptions), - fmt.Sprintf( - "redemptionKey: [%s], requested at: [%s]", - pendingRedemption.RedemptionKey, - pendingRedemption.RequestedAt, - ), - ) - result[walletPublicKeyHash] = append( - result[walletPublicKeyHash], - pendingRedemption.RedeemerOutputScript, - ) - } - - // Apply the wallets number limit if needed. - if limit := int(filter.WalletsLimit); limit > 0 && len(result) == limit { - logger.Infof( - "aborting pending redemptions checks due to the "+ - "configured wallets limit; [%v] wallets with pending "+ - "redemptions were found so far", - len(result), - ) - break - } + result = append(result, pendingRedemption.RedeemerOutputScript) } return result, nil } // ProposeRedemption returns a redemption proposal. -func ProposeRedemption( - chain Chain, - btcChain bitcoin.Chain, +func (rt *RedemptionTask) ProposeRedemption( + taskLogger log.StandardLogger, walletPublicKeyHash [20]byte, - fee int64, redeemersOutputScripts []bitcoin.Script, + fee int64, ) (*tbtc.RedemptionProposal, error) { if len(redeemersOutputScripts) == 0 { return nil, fmt.Errorf("redemptions list is empty") } - logger.Infof( - "starting proposing redemption for wallet [%s]...", - hex.EncodeToString(walletPublicKeyHash[:]), - ) + taskLogger.Infof("preparing a redemption proposal") // Estimate fee if it's missing. Do not check the estimated fee against // the maximum total and per-request fees allowed by the Bridge. This // is done during the on-chain validation of the proposal so there is no // need to do it here. if fee <= 0 { - logger.Infof("estimating redemption transaction fee...") + taskLogger.Infof("estimating redemption transaction fee") estimatedFee, err := EstimateRedemptionFee( - btcChain, + rt.btcChain, redeemersOutputScripts, ) if err != nil { @@ -330,9 +210,7 @@ func ProposeRedemption( fee = estimatedFee } - logger.Infof("redemption transaction fee: [%d]", fee) - - logger.Infof("preparing a redemption proposal...") + taskLogger.Infof("redemption transaction fee: [%d]", fee) proposal := &tbtc.RedemptionProposal{ WalletPublicKeyHash: walletPublicKeyHash, @@ -340,12 +218,12 @@ func ProposeRedemption( RedemptionTxFee: big.NewInt(fee), } - logger.Infof("validating the redemption proposal...") + taskLogger.Infof("validating the redemption proposal") if _, err := tbtc.ValidateRedemptionProposal( - logger, + taskLogger, proposal, - chain, + rt.chain, ); err != nil { return nil, fmt.Errorf("failed to verify redemption proposal: %v", err) } @@ -353,14 +231,14 @@ func ProposeRedemption( return proposal, nil } -func getPendingRedemptions( +func findPendingRedemptions( + fnLogger log.StandardLogger, chain Chain, walletPublicKeyHash [20]byte, currentBlockNumber uint64, requestsLimit uint16, requestTimeout uint32, requestMinAge uint32, - requestAmountLimit uint64, ) ([]*RedemptionRequest, error) { // We are interested with `RedemptionRequested` events that are not // timed out yet. That means there is no sense to look for events that @@ -424,9 +302,9 @@ func getPendingRedemptions( eventsSet[hexutils.Encode(redemptionKey.Bytes())] = event } - logger.Infof("found [%d] RedemptionRequested events", len(eventsSet)) + fnLogger.Infof("found [%d] RedemptionRequested events", len(eventsSet)) - logger.Infof("checking pending redemptions details...") + fnLogger.Infof("checking pending redemptions details") pendingRedemptions := make([]*RedemptionRequest, 0) @@ -435,10 +313,9 @@ redemptionRequestedLoop: for redemptionKey, event := range eventsSet { eventIndex++ - logger.Debugf( - "getting pending redemption details [%d/%d]", - eventIndex, - len(eventsSet), + fnLogger.Debugf( + "getting pending redemption details [%s]", + redemptionKey, ) // Check if there is still a pending redemption for the given redemption @@ -454,10 +331,9 @@ redemptionRequestedLoop: ) } if !found { - logger.Infof( - "redemption for request [%d/%d] is no longer pending", - eventIndex, - len(eventsSet), + fnLogger.Infof( + "redemption request [%s] is no longer pending", + redemptionKey, ) continue redemptionRequestedLoop @@ -497,36 +373,25 @@ redemptionRequestedLoop: ) result := make([]*RedemptionRequest, 0, resultSliceCapacity) - for i, pendingRedemption := range pendingRedemptions { + for _, pendingRedemption := range pendingRedemptions { if len(result) == cap(result) { break } // Check if timeout passed for the redemption request. if pendingRedemption.RequestedAt.Before(redemptionRequestsRangeStartTimestamp) { - logger.Infof( - "redemption request [%d/%d] has already timed out", - i+1, - len(pendingRedemptions), + fnLogger.Infof( + "redemption request [%s] has already timed out", + pendingRedemption.RedemptionKey, ) continue } // Check if enough time elapsed since the redemption request. if pendingRedemption.RequestedAt.After(redemptionRequestsRangeEndTimestamp) { - logger.Infof( - "redemption request [%d/%d] is not old enough", - i+1, - len(pendingRedemptions), - ) - continue - } - - if requestAmountLimit > 0 && pendingRedemption.RequestedAmount > requestAmountLimit { - logger.Infof( - "redemption request [%d/%d] exceeds the request amount limit", - i+1, - len(pendingRedemptions), + fnLogger.Infof( + "redemption request [%s] is not old enough", + pendingRedemption.RedemptionKey, ) continue } diff --git a/pkg/tbtcpg/redemptions_test.go b/pkg/tbtcpg/redemptions_test.go index 48f6a9ddec..38ffafd469 100644 --- a/pkg/tbtcpg/redemptions_test.go +++ b/pkg/tbtcpg/redemptions_test.go @@ -42,7 +42,7 @@ func TestEstimateRedemptionFee(t *testing.T) { testutils.AssertIntsEqual(t, "fee", expectedFee, int(actualFee)) } -func TestFindPendingRedemptions(t *testing.T) { +func TestRedemptionAction_FindPendingRedemptions(t *testing.T) { scenarios, err := test.LoadFindPendingRedemptionsTestScenario() if err != nil { t.Fatal(err) @@ -78,20 +78,6 @@ func TestFindPendingRedemptions(t *testing.T) { requestTimeoutBlocks := uint64(scenario.ChainParameters.RequestTimeout) / uint64(scenario.ChainParameters.AverageBlockTime.Seconds()) - // Record scenario wallets to the local chain. - for _, wallet := range scenario.Wallets { - err := tbtcChain.AddPastNewWalletRegisteredEvent( - nil, - &tbtc.NewWalletRegisteredEvent{ - WalletPublicKeyHash: wallet.WalletPublicKeyHash, - BlockNumber: wallet.RegistrationBlockNumber, - }, - ) - if err != nil { - t.Fatal(err) - } - } - // Record scenario pending redemptions to the local chain. for _, pendingRedemption := range scenario.PendingRedemptions { // Record the corresponding event. Set only relevant fields. @@ -121,17 +107,20 @@ func TestFindPendingRedemptions(t *testing.T) { ) } - walletsPendingRedemptions, err := tbtcpg.FindPendingRedemptions( - tbtcChain, - scenario.Filter, + task := tbtcpg.NewRedemptionTask(tbtcChain, nil) + + redeemersOutputScripts, err := task.FindPendingRedemptions( + &testutils.MockLogger{}, + scenario.WalletPublicKeyHash, + scenario.MaxNumberOfRequests, ) if err != nil { t.Fatal(err) } if diff := deep.Equal( - scenario.ExpectedWalletsPendingRedemptions, - walletsPendingRedemptions, + scenario.ExpectedRedeemersOutputScripts, + redeemersOutputScripts, ); diff != nil { t.Errorf("invalid wallets pending redemptions: %v", diff) } @@ -139,7 +128,7 @@ func TestFindPendingRedemptions(t *testing.T) { } } -func TestProposeRedemption(t *testing.T) { +func TestRedemptionAction_ProposeRedemption(t *testing.T) { fromHex := func(hexString string) []byte { bytes, err := hex.DecodeString(hexString) if err != nil { @@ -202,21 +191,19 @@ func TestProposeRedemption(t *testing.T) { t.Fatal(err) } - proposal, err := tbtcpg.ProposeRedemption( - tbtcChain, - btcChain, + task := tbtcpg.NewRedemptionTask(tbtcChain, btcChain) + + proposal, err := task.ProposeRedemption( + &testutils.MockLogger{}, walletPublicKeyHash, - test.fee, redeemersOutputScripts, + test.fee, ) if err != nil { t.Fatal(err) } - if diff := deep.Equal( - []*tbtc.RedemptionProposal{proposal}, - []*tbtc.RedemptionProposal{test.expectedProposal}, - ); diff != nil { + if diff := deep.Equal(proposal, test.expectedProposal); diff != nil { t.Errorf("invalid deposits: %v", diff) } }) diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index 9831ee35a1..92449ff1c6 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -11,20 +11,20 @@ import ( var logger = log.Logger("keep-tbtcpg") -// proposalTask encapsulates logic used to generate an action proposal +// ProposalTask encapsulates logic used to generate an action proposal // of the given type. -type proposalTask interface { - // run executes the task and returns a proposal, a boolean flag indicating +type ProposalTask interface { + // Run executes the task and returns a proposal, a boolean flag indicating // whether the proposal was generated and an error if any. - run(walletPublicKeyHash [20]byte) (tbtc.CoordinationProposal, bool, error) - // actionType returns the type of the action proposal. - actionType() tbtc.WalletActionType + Run(walletPublicKeyHash [20]byte) (tbtc.CoordinationProposal, bool, error) + // ActionType returns the type of the action proposal. + ActionType() tbtc.WalletActionType } // ProposalGenerator is a component responsible for generating coordination // proposals for tbtc wallets. type ProposalGenerator struct { - tasks []proposalTask + tasks []ProposalTask } // NewProposalGenerator returns a new proposal generator. @@ -32,9 +32,9 @@ func NewProposalGenerator( chain Chain, btcChain bitcoin.Chain, ) *ProposalGenerator { - tasks := []proposalTask{ - newDepositSweepTask(chain, btcChain), - newRedemptionTask(chain, btcChain), + tasks := []ProposalTask{ + NewDepositSweepTask(chain, btcChain), + NewRedemptionTask(chain, btcChain), // newHeartbeatTask(chain, btcChain), // TODO: Uncomment when moving funds support is implemented. // newMovedFundsSweepTask(), @@ -68,8 +68,8 @@ func (pg *ProposalGenerator) Generate( for _, action := range actionsChecklist { walletLogger.Infof("starting proposal task [%s]", action) - taskIndex := slices.IndexFunc(pg.tasks, func(task proposalTask) bool { - return task.actionType() == action + taskIndex := slices.IndexFunc(pg.tasks, func(task ProposalTask) bool { + return task.ActionType() == action }) if taskIndex < 0 { @@ -77,7 +77,7 @@ func (pg *ProposalGenerator) Generate( continue } - proposal, ok, err := pg.tasks[taskIndex].run(walletPublicKeyHash) + proposal, ok, err := pg.tasks[taskIndex].Run(walletPublicKeyHash) if err != nil { return nil, fmt.Errorf( "error while running proposal task [%s]: [%v]", diff --git a/pkg/tbtcpg/tbtcpg_test.go b/pkg/tbtcpg/tbtcpg_test.go index 41d5d96720..b90cf238c4 100644 --- a/pkg/tbtcpg/tbtcpg_test.go +++ b/pkg/tbtcpg/tbtcpg_test.go @@ -11,13 +11,13 @@ func TestProposalGenerator_Generate(t *testing.T) { walletPublicKeyHash := [20]byte{1, 2, 3} tests := map[string]struct { - tasks []proposalTask + tasks []ProposalTask actionsChecklist []tbtc.WalletActionType expectedProposal tbtc.CoordinationProposal expectedErr error }{ "first task generates a proposal": { - tasks: []proposalTask{ + tasks: []ProposalTask{ &mockProposalTask{ action: tbtc.ActionRedemption, results: map[[20]byte]mockProposalTaskResult{ @@ -38,7 +38,7 @@ func TestProposalGenerator_Generate(t *testing.T) { expectedProposal: &mockCoordinationProposal{tbtc.ActionRedemption}, }, "subsequent task generates a proposal": { - tasks: []proposalTask{ + tasks: []ProposalTask{ &mockProposalTask{ action: tbtc.ActionRedemption, results: map[[20]byte]mockProposalTaskResult{ @@ -59,7 +59,7 @@ func TestProposalGenerator_Generate(t *testing.T) { expectedProposal: &mockCoordinationProposal{tbtc.ActionDepositSweep}, }, "first task returns error": { - tasks: []proposalTask{ + tasks: []ProposalTask{ &mockProposalTask{ action: tbtc.ActionRedemption, results: map[[20]byte]mockProposalTaskResult{ @@ -81,7 +81,7 @@ func TestProposalGenerator_Generate(t *testing.T) { expectedErr: fmt.Errorf("error while running proposal task [Redemption]: [proposal task error]"), }, "first task is unsupported": { - tasks: []proposalTask{ + tasks: []ProposalTask{ &mockProposalTask{ action: tbtc.ActionDepositSweep, results: map[[20]byte]mockProposalTaskResult{ @@ -96,7 +96,7 @@ func TestProposalGenerator_Generate(t *testing.T) { expectedProposal: &mockCoordinationProposal{tbtc.ActionDepositSweep}, }, "all tasks complete without result": { - tasks: []proposalTask{ + tasks: []ProposalTask{ &mockProposalTask{ action: tbtc.ActionRedemption, results: map[[20]byte]mockProposalTaskResult{ @@ -167,7 +167,7 @@ type mockProposalTask struct { results map[[20]byte]mockProposalTaskResult } -func (mpt *mockProposalTask) run(walletPublicKeyHash [20]byte) ( +func (mpt *mockProposalTask) Run(walletPublicKeyHash [20]byte) ( tbtc.CoordinationProposal, bool, error, @@ -189,7 +189,7 @@ func (mpt *mockProposalTask) run(walletPublicKeyHash [20]byte) ( } } -func (mpt *mockProposalTask) actionType() tbtc.WalletActionType { +func (mpt *mockProposalTask) ActionType() tbtc.WalletActionType { return mpt.action } From 13e1044f1b750f99949c74ca35c9ff3dbdc05922 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 1 Dec 2023 15:34:40 +0100 Subject: [PATCH 10/14] Cleanup unused chain functions --- pkg/chain/ethereum/tbtc.go | 57 ------------------------- pkg/tbtcpg/chain.go | 22 ---------- pkg/tbtcpg/chain_test.go | 85 -------------------------------------- 3 files changed, 164 deletions(-) diff --git a/pkg/chain/ethereum/tbtc.go b/pkg/chain/ethereum/tbtc.go index f4a27b4ffd..e706320621 100644 --- a/pkg/chain/ethereum/tbtc.go +++ b/pkg/chain/ethereum/tbtc.go @@ -1791,63 +1791,6 @@ func (tc *TbtcChain) ValidateDepositSweepProposal( return nil } -func (tc *TbtcChain) SubmitDepositSweepProposalWithReimbursement( - proposal *tbtc.DepositSweepProposal, -) error { - gasEstimate, err := tc.walletCoordinator.SubmitDepositSweepProposalWithReimbursementGasEstimate( - convertDepositSweepProposalToAbiType(proposal), - ) - if err != nil { - return err - } - - // The original estimate for this contract call is too low and the call - // fails on reimbursing the submitter. Examples: - // 0x5711df32d785140ca6b5b12c87f818a6c5d75d10445a12a7d3d75caadb40c0ac - // 0xf9a8c0b0ecceb673e19eed7af7c9963cdd929468fb3818e9a8c3b8c59dc6ef85 - // Here we add a 20% margin to overcome the gas problems. - gasEstimateWithMargin := float64(gasEstimate) * float64(1.2) - - _, err = tc.walletCoordinator.SubmitDepositSweepProposalWithReimbursement( - convertDepositSweepProposalToAbiType(proposal), - ethutil.TransactionOptions{ - GasLimit: uint64(gasEstimateWithMargin), - }, - ) - - return err -} - -func (tc *TbtcChain) SubmitRedemptionProposalWithReimbursement( - proposal *tbtc.RedemptionProposal, -) error { - abiProposal, err := convertRedemptionProposalToAbiType(proposal) - if err != nil { - return fmt.Errorf("cannot convert proposal to abi type: [%v]", err) - } - - gasEstimate, err := tc.walletCoordinator.SubmitRedemptionProposalWithReimbursementGasEstimate( - abiProposal, - ) - if err != nil { - return err - } - - // The original estimate for this contract call is too low and the call - // fails on reimbursing the submitter. Here we add a 20% margin to overcome - // the gas problems. - gasEstimateWithMargin := float64(gasEstimate) * float64(1.2) - - _, err = tc.walletCoordinator.SubmitRedemptionProposalWithReimbursement( - abiProposal, - ethutil.TransactionOptions{ - GasLimit: uint64(gasEstimateWithMargin), - }, - ) - - return err -} - func (tc *TbtcChain) GetDepositSweepMaxSize() (uint16, error) { return tc.walletCoordinator.DepositSweepMaxSize() } diff --git a/pkg/tbtcpg/chain.go b/pkg/tbtcpg/chain.go index 557d1d8a54..e616a5bc26 100644 --- a/pkg/tbtcpg/chain.go +++ b/pkg/tbtcpg/chain.go @@ -48,14 +48,6 @@ type Chain interface { err error, ) - // SubmitDepositSweepProposalWithReimbursement submits a deposit sweep - // proposal to the chain. It reimburses the gas cost to the caller. - // - // TODO: Remove this method. - SubmitDepositSweepProposalWithReimbursement( - proposal *tbtc.DepositSweepProposal, - ) error - // PastRedemptionRequestedEvents fetches past redemption requested events according // to the provided filter or unfiltered if the filter is nil. Returned // events are sorted by the block number in the ascending order, i.e. the @@ -85,12 +77,6 @@ type Chain interface { err error, ) - // SubmitRedemptionProposalWithReimbursement submits a redemption proposal - // to the chain. It reimburses the gas cost to the caller. - SubmitRedemptionProposalWithReimbursement( - proposal *tbtc.RedemptionProposal, - ) error - // GetRedemptionMaxSize gets the maximum number of redemption requests that // can be a part of a redemption sweep proposal. GetRedemptionMaxSize() (uint16, error) @@ -137,14 +123,6 @@ type Chain interface { // be part of a deposit sweep proposal. GetDepositSweepMaxSize() (uint16, error) - // GetWalletLock gets the current wallet lock for the given wallet. - // Returned values represent the expiration time and the cause of the lock. - // The expiration time can be UNIX timestamp 0 which means there is no lock - // on the wallet at the given moment. - GetWalletLock( - walletPublicKeyHash [20]byte, - ) (time.Time, tbtc.WalletActionType, error) - BlockCounter() (chain.BlockCounter, error) AverageBlockTime() time.Duration diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index 3d5b938089..f264c91c3d 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -16,11 +16,6 @@ import ( "github.com/keep-network/keep-core/pkg/tbtc" ) -type walletLock struct { - lockExpiration time.Time - walletAction tbtc.WalletActionType -} - type depositParameters = struct { dustThreshold uint64 treasuryFeeDivisor uint64 @@ -46,15 +41,12 @@ type LocalChain struct { pastNewWalletRegisteredEvents map[[32]byte][]*tbtc.NewWalletRegisteredEvent depositParameters depositParameters depositSweepProposalValidations map[[32]byte]bool - depositSweepProposals []*tbtc.DepositSweepProposal - walletLocks map[[20]byte]*walletLock redemptionParameters redemptionParameters redemptionRequestMinAge uint32 blockCounter chain.BlockCounter pastRedemptionRequestedEvents map[[32]byte][]*tbtc.RedemptionRequestedEvent averageBlockTime time.Duration pendingRedemptionRequests map[[32]byte]*tbtc.RedemptionRequest - redemptionProposals []*tbtc.RedemptionProposal redemptionProposalValidations map[[32]byte]bool } @@ -64,27 +56,12 @@ func NewLocalChain() *LocalChain { pastDepositRevealedEvents: make(map[[32]byte][]*tbtc.DepositRevealedEvent), pastNewWalletRegisteredEvents: make(map[[32]byte][]*tbtc.NewWalletRegisteredEvent), depositSweepProposalValidations: make(map[[32]byte]bool), - walletLocks: make(map[[20]byte]*walletLock), pastRedemptionRequestedEvents: make(map[[32]byte][]*tbtc.RedemptionRequestedEvent), pendingRedemptionRequests: make(map[[32]byte]*tbtc.RedemptionRequest), redemptionProposalValidations: make(map[[32]byte]bool), } } -func (lc *LocalChain) DepositSweepProposals() []*tbtc.DepositSweepProposal { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - return lc.depositSweepProposals -} - -func (lc *LocalChain) RedemptionProposals() []*tbtc.RedemptionProposal { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - return lc.redemptionProposals -} - func (lc *LocalChain) PastDepositRevealedEvents( filter *tbtc.DepositRevealedEventFilter, ) ([]*tbtc.DepositRevealedEvent, error) { @@ -551,28 +528,6 @@ func buildDepositSweepProposalValidationKey( return sha256.Sum256(buffer.Bytes()), nil } -func (lc *LocalChain) SubmitDepositSweepProposalWithReimbursement( - proposal *tbtc.DepositSweepProposal, -) error { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - lc.depositSweepProposals = append(lc.depositSweepProposals, proposal) - - return nil -} - -func (lc *LocalChain) SubmitRedemptionProposalWithReimbursement( - proposal *tbtc.RedemptionProposal, -) error { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - lc.redemptionProposals = append(lc.redemptionProposals, proposal) - - return nil -} - func (lc *LocalChain) ValidateRedemptionProposal( proposal *tbtc.RedemptionProposal, ) error { @@ -651,46 +606,6 @@ func (lc *LocalChain) GetDepositSweepMaxSize() (uint16, error) { panic("unsupported") } -func (lc *LocalChain) GetWalletLock( - walletPublicKeyHash [20]byte, -) (time.Time, tbtc.WalletActionType, error) { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - walletLock, ok := lc.walletLocks[walletPublicKeyHash] - if !ok { - return time.Time{}, tbtc.ActionNoop, fmt.Errorf("no lock configured for given wallet") - } - - return walletLock.lockExpiration, walletLock.walletAction, nil -} - -func (lc *LocalChain) SetWalletLock( - walletPublicKeyHash [20]byte, - lockExpiration time.Time, - walletAction tbtc.WalletActionType, -) { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - lc.walletLocks[walletPublicKeyHash] = &walletLock{ - lockExpiration: lockExpiration, - walletAction: walletAction, - } -} - -func (lc *LocalChain) ResetWalletLock( - walletPublicKeyHash [20]byte, -) { - lc.mutex.Lock() - defer lc.mutex.Unlock() - - lc.walletLocks[walletPublicKeyHash] = &walletLock{ - lockExpiration: time.Unix(0, 0), - walletAction: tbtc.ActionNoop, - } -} - func (lc *LocalChain) BlockCounter() (chain.BlockCounter, error) { lc.mutex.Lock() defer lc.mutex.Unlock() From 3be4a766773aa3cfb9d9c2e20aff136a062bc5b3 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 1 Dec 2023 16:15:49 +0100 Subject: [PATCH 11/14] Update docstring of `redemptionTask.FindPendingRedemptions` function --- pkg/tbtcpg/redemptions.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/tbtcpg/redemptions.go b/pkg/tbtcpg/redemptions.go index 54fc6aac1e..9cfb617db2 100644 --- a/pkg/tbtcpg/redemptions.go +++ b/pkg/tbtcpg/redemptions.go @@ -93,11 +93,12 @@ type RedemptionRequest struct { RequestedAmount uint64 } -// FindPendingRedemptions finds pending redemptions requests according to -// the provided filter. The returned value is a map, where the key is -// a 20-byte public key hash of a specific wallet and the value is a list -// of pending requests targeting this wallet. It is guaranteed that an existing -// key has always a non-empty slice as value. +// FindPendingRedemptions finds pending redemptions requests for the +// provided wallet. The returned value is a list of redeemers output +// scripts that come from detected pending requests targeting this wallet. +// The maxNumberOfRequests parameter is used as a ceiling for the number of +// requests in the result. If number of discovered requests meets the +// maxNumberOfRequests the function will stop fetching more requests. func (rt *RedemptionTask) FindPendingRedemptions( taskLogger log.StandardLogger, walletPublicKeyHash [20]byte, From cd051443d3919db96e91a60c66822ffc17705ecb Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 1 Dec 2023 16:42:04 +0100 Subject: [PATCH 12/14] Heartbeat proposal generation --- pkg/chain/ethereum/tbtc.go | 7 +++ pkg/tbtc/coordination_test.go | 6 +-- pkg/tbtc/heartbeat.go | 2 +- pkg/tbtc/marshaling.go | 14 +++++- pkg/tbtc/marshaling_test.go | 2 +- pkg/tbtcpg/chain.go | 5 +++ pkg/tbtcpg/chain_test.go | 30 +++++++++++++ pkg/tbtcpg/heartbeat.go | 61 ++++++++++++++++++++++++++ pkg/tbtcpg/heartbeat_test.go | 82 +++++++++++++++++++++++++++++++++++ pkg/tbtcpg/tbtcpg.go | 2 +- 10 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 pkg/tbtcpg/heartbeat.go create mode 100644 pkg/tbtcpg/heartbeat_test.go diff --git a/pkg/chain/ethereum/tbtc.go b/pkg/chain/ethereum/tbtc.go index e706320621..0721957b8a 100644 --- a/pkg/chain/ethereum/tbtc.go +++ b/pkg/chain/ethereum/tbtc.go @@ -1912,3 +1912,10 @@ func (tc *TbtcChain) GetRedemptionMaxSize() (uint16, error) { func (tc *TbtcChain) GetRedemptionRequestMinAge() (uint32, error) { return tc.walletCoordinator.RedemptionRequestMinAge() } + +func (tc *TbtcChain) ValidateHeartbeatProposal( + proposal *tbtc.HeartbeatProposal, +) error { + // TODO: Implementation. + panic("not implemented yet") +} diff --git a/pkg/tbtc/coordination_test.go b/pkg/tbtc/coordination_test.go index d8a6ea53b3..093d3c8fee 100644 --- a/pkg/tbtc/coordination_test.go +++ b/pkg/tbtc/coordination_test.go @@ -703,7 +703,7 @@ func TestCoordinationExecutor_ExecuteLeaderRoutine(t *testing.T) { for _, action := range actionsChecklist { if walletPublicKeyHash == publicKeyHash && action == ActionHeartbeat { return &HeartbeatProposal{ - Message: []byte("heartbeat message"), + Message: [16]byte{0x01, 0x02}, }, nil } } @@ -764,7 +764,7 @@ func TestCoordinationExecutor_ExecuteLeaderRoutine(t *testing.T) { <-ctx.Done() expectedProposal := &HeartbeatProposal{ - Message: []byte("heartbeat message"), + Message: [16]byte{0x01, 0x02}, } if !reflect.DeepEqual(expectedProposal, proposal) { @@ -990,7 +990,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine(t *testing.T) { coordinationBlock: 900, walletPublicKeyHash: executor.walletPublicKeyHash(), proposal: &HeartbeatProposal{ - Message: []byte("heartbeat message"), + Message: [16]byte{0x01, 0x02}, }, }) if err != nil { diff --git a/pkg/tbtc/heartbeat.go b/pkg/tbtc/heartbeat.go index b4945d91d4..292c78a29b 100644 --- a/pkg/tbtc/heartbeat.go +++ b/pkg/tbtc/heartbeat.go @@ -31,7 +31,7 @@ const ( ) type HeartbeatProposal struct { - Message []byte + Message [16]byte } func (hp *HeartbeatProposal) ActionType() WalletActionType { diff --git a/pkg/tbtc/marshaling.go b/pkg/tbtc/marshaling.go index 88dacb5183..eb6899dc45 100644 --- a/pkg/tbtc/marshaling.go +++ b/pkg/tbtc/marshaling.go @@ -261,7 +261,7 @@ func (np *NoopProposal) Unmarshal([]byte) error { func (hp *HeartbeatProposal) Marshal() ([]byte, error) { return proto.Marshal( &pb.HeartbeatProposal{ - Message: hp.Message, + Message: hp.Message[:], }, ) } @@ -273,7 +273,17 @@ func (hp *HeartbeatProposal) Unmarshal(bytes []byte) error { return fmt.Errorf("failed to unmarshal HeartbeatProposal: [%v]", err) } - hp.Message = pbMsg.Message + if len(pbMsg.Message) != 16 { + return fmt.Errorf( + "invalid heartbeat message length: [%v]", + len(pbMsg.Message), + ) + } + + var message [16]byte + copy(message[:], pbMsg.Message) + + hp.Message = message return nil } diff --git a/pkg/tbtc/marshaling_test.go b/pkg/tbtc/marshaling_test.go index a2fbb73996..9092eb2ef4 100644 --- a/pkg/tbtc/marshaling_test.go +++ b/pkg/tbtc/marshaling_test.go @@ -134,7 +134,7 @@ func TestCoordinationMessage_MarshalingRoundtrip(t *testing.T) { }, "with heartbeat proposal": { proposal: &HeartbeatProposal{ - Message: []byte("heartbeat message"), + Message: [16]byte{0x01, 0x02}, }, }, "with deposit sweep proposal": { diff --git a/pkg/tbtcpg/chain.go b/pkg/tbtcpg/chain.go index e616a5bc26..958e54fee3 100644 --- a/pkg/tbtcpg/chain.go +++ b/pkg/tbtcpg/chain.go @@ -126,4 +126,9 @@ type Chain interface { BlockCounter() (chain.BlockCounter, error) AverageBlockTime() time.Duration + + // ValidateHeartbeatProposal validates the given heartbeat proposal + // against the chain. Returns an error if the proposal is not valid or + // nil otherwise. + ValidateHeartbeatProposal(proposal *tbtc.HeartbeatProposal) error } diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index f264c91c3d..1d0fafed15 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -48,6 +48,7 @@ type LocalChain struct { averageBlockTime time.Duration pendingRedemptionRequests map[[32]byte]*tbtc.RedemptionRequest redemptionProposalValidations map[[32]byte]bool + heartbeatProposalValidations map[[16]byte]bool } func NewLocalChain() *LocalChain { @@ -59,6 +60,7 @@ func NewLocalChain() *LocalChain { pastRedemptionRequestedEvents: make(map[[32]byte][]*tbtc.RedemptionRequestedEvent), pendingRedemptionRequests: make(map[[32]byte]*tbtc.RedemptionRequest), redemptionProposalValidations: make(map[[32]byte]bool), + heartbeatProposalValidations: make(map[[16]byte]bool), } } @@ -568,6 +570,34 @@ func (lc *LocalChain) SetRedemptionProposalValidationResult( return nil } +func (lc *LocalChain) ValidateHeartbeatProposal( + proposal *tbtc.HeartbeatProposal, +) error { + lc.mutex.Lock() + defer lc.mutex.Unlock() + + result, ok := lc.heartbeatProposalValidations[proposal.Message] + if !ok { + return fmt.Errorf("validation result unknown") + } + + if !result { + return fmt.Errorf("validation failed") + } + + return nil +} + +func (lc *LocalChain) SetHeartbeatProposalValidationResult( + proposal *tbtc.HeartbeatProposal, + result bool, +) { + lc.mutex.Lock() + defer lc.mutex.Unlock() + + lc.heartbeatProposalValidations[proposal.Message] = result +} + func buildRedemptionProposalValidationKey( proposal *tbtc.RedemptionProposal, ) ([32]byte, error) { diff --git a/pkg/tbtcpg/heartbeat.go b/pkg/tbtcpg/heartbeat.go new file mode 100644 index 0000000000..2d00e51603 --- /dev/null +++ b/pkg/tbtcpg/heartbeat.go @@ -0,0 +1,61 @@ +package tbtcpg + +import ( + "crypto/sha256" + "encoding/binary" + "fmt" + "github.com/keep-network/keep-core/pkg/tbtc" +) + +// HeartbeatTask is a task that may produce a heartbeat proposal. +type HeartbeatTask struct { + chain Chain +} + +func NewHeartbeatTask(chain Chain) *HeartbeatTask { + return &HeartbeatTask{ + chain: chain, + } +} + +func (ht *HeartbeatTask) Run(walletPublicKeyHash [20]byte) ( + tbtc.CoordinationProposal, + bool, + error, +) { + blockCounter, err := ht.chain.BlockCounter() + if err != nil { + return nil, false, fmt.Errorf("failed to get block counter: [%v]", err) + } + + block, err := blockCounter.CurrentBlock() + if err != nil { + return nil, false, fmt.Errorf("failed to get current block: [%v]", err) + } + blockBytes := make([]byte, 8) + binary.BigEndian.PutUint64(blockBytes, block) + + hash := sha256.Sum256(append(walletPublicKeyHash[:], blockBytes...)) + + message := [16]byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + } + + proposal := &tbtc.HeartbeatProposal{ + Message: message, + } + + if err := ht.chain.ValidateHeartbeatProposal(proposal); err != nil { + return nil, false, fmt.Errorf( + "failed to verify heartbeat proposal: [%v]", + err, + ) + } + + return proposal, true, nil +} + +func (ht *HeartbeatTask) ActionType() tbtc.WalletActionType { + return tbtc.ActionHeartbeat +} diff --git a/pkg/tbtcpg/heartbeat_test.go b/pkg/tbtcpg/heartbeat_test.go new file mode 100644 index 0000000000..a8abff67d8 --- /dev/null +++ b/pkg/tbtcpg/heartbeat_test.go @@ -0,0 +1,82 @@ +package tbtcpg + +import ( + "fmt" + "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/tbtc" + "reflect" + "testing" +) + +func TestHeartbeatTask_Run(t *testing.T) { + tests := map[string]struct { + validationResult bool + expectedProposal tbtc.CoordinationProposal + expectedOk bool + expectedErr error + }{ + "valid proposal": { + validationResult: true, + expectedProposal: &tbtc.HeartbeatProposal{ + Message: [16]byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0xd7, 0x5a, 0xec, 0xd2, 0x9e, 0x5b, 0xca, + }, + }, + expectedOk: true, + expectedErr: nil, + }, + "invalid proposal": { + validationResult: false, + expectedProposal: nil, + expectedOk: false, + expectedErr: fmt.Errorf( + "failed to verify heartbeat proposal: [validation failed]", + ), + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + tbtcChain := NewLocalChain() + blockCounter := NewMockBlockCounter() + + blockCounter.SetCurrentBlock(900) + tbtcChain.SetBlockCounter(blockCounter) + + tbtcChain.SetHeartbeatProposalValidationResult( + &tbtc.HeartbeatProposal{ + Message: [16]byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0xd7, 0x5a, 0xec, 0xd2, 0x9e, 0x5b, 0xca, + }, + }, + test.validationResult, + ) + + walletPublicKeyHash := [20]byte{0x01, 0x02} + + task := NewHeartbeatTask(tbtcChain) + + proposal, ok, err := task.Run(walletPublicKeyHash) + + if !reflect.DeepEqual(test.expectedErr, err) { + t.Errorf( + "unexpected error\nexpected: [%v]\nactual: [%v]", + test.expectedErr, + err, + ) + } + + testutils.AssertBoolsEqual(t, "boolean flag", test.expectedOk, ok) + + if !reflect.DeepEqual(test.expectedProposal, proposal) { + t.Errorf( + "unexpected proposal\nexpected: [%v]\nactual: [%v]", + test.expectedProposal, + proposal, + ) + } + }) + } +} diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index 92449ff1c6..9958997732 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -35,7 +35,7 @@ func NewProposalGenerator( tasks := []ProposalTask{ NewDepositSweepTask(chain, btcChain), NewRedemptionTask(chain, btcChain), - // newHeartbeatTask(chain, btcChain), + NewHeartbeatTask(chain), // TODO: Uncomment when moving funds support is implemented. // newMovedFundsSweepTask(), // newMovingFundsTask(), From 723c824fc16634a940f375c0028e5839582d672b Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 4 Dec 2023 12:00:06 +0100 Subject: [PATCH 13/14] Pass more data to the proposal generator Some proposals may need some additional data about the wallet (e.g. moving funds). Here we are trying to satisfy them by introducing a `CoordinationProposalRequest` type that is used to feed the generator with necessary data. --- pkg/tbtc/coordination.go | 24 ++++++++++++++++-------- pkg/tbtc/coordination_test.go | 16 ++++++++-------- pkg/tbtcpg/chain.go | 35 ++++------------------------------- pkg/tbtcpg/chain_test.go | 11 +++++++++++ pkg/tbtcpg/deposit_sweep.go | 9 ++++++--- pkg/tbtcpg/heartbeat.go | 5 ++++- pkg/tbtcpg/heartbeat_test.go | 12 +++++++++--- pkg/tbtcpg/redemptions.go | 9 ++++++--- pkg/tbtcpg/tbtcpg.go | 20 +++++++++++++------- pkg/tbtcpg/tbtcpg_test.go | 16 +++++++++++----- 10 files changed, 88 insertions(+), 69 deletions(-) diff --git a/pkg/tbtc/coordination.go b/pkg/tbtc/coordination.go index d03860a9dd..e267106dcc 100644 --- a/pkg/tbtc/coordination.go +++ b/pkg/tbtc/coordination.go @@ -5,11 +5,12 @@ import ( "crypto/sha256" "encoding/binary" "fmt" + "math/rand" + "sort" + "github.com/keep-network/keep-core/pkg/internal/pb" "go.uber.org/zap" "golang.org/x/exp/slices" - "math/rand" - "sort" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" @@ -195,6 +196,13 @@ func (cf *coordinationFault) String() string { ) } +// CoordinationProposalRequest represents a request for a coordination proposal. +type CoordinationProposalRequest struct { + WalletPublicKeyHash [20]byte + WalletOperators []chain.Address + ActionsChecklist []WalletActionType +} + // CoordinationProposalGenerator is a component responsible for generating // coordination proposals. type CoordinationProposalGenerator interface { @@ -204,10 +212,7 @@ type CoordinationProposalGenerator interface { // expected to return a proposal for the first action from the checklist // that is valid for the given wallet's state. If none of the actions are // valid, the generator should return a no-op proposal. - Generate( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, - ) (CoordinationProposal, error) + Generate(request *CoordinationProposalRequest) (CoordinationProposal, error) } // CoordinationProposal represents a single action proposal for the given wallet. @@ -558,8 +563,11 @@ func (ce *coordinationExecutor) executeLeaderRoutine( walletPublicKeyHash := ce.walletPublicKeyHash() proposal, err := ce.proposalGenerator.Generate( - walletPublicKeyHash, - actionsChecklist, + &CoordinationProposalRequest{ + WalletPublicKeyHash: walletPublicKeyHash, + WalletOperators: ce.coordinatedWallet.signingGroupOperators, + ActionsChecklist: actionsChecklist, + }, ) if err != nil { return nil, fmt.Errorf("failed to generate proposal: [%v]", err) diff --git a/pkg/tbtc/coordination_test.go b/pkg/tbtc/coordination_test.go index 093d3c8fee..729bc55fbf 100644 --- a/pkg/tbtc/coordination_test.go +++ b/pkg/tbtc/coordination_test.go @@ -6,6 +6,12 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "math/big" + "math/rand" + "reflect" + "testing" + "time" + "github.com/go-test/deep" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/chain" @@ -17,11 +23,6 @@ import ( "github.com/keep-network/keep-core/pkg/protocol/group" "github.com/keep-network/keep-core/pkg/tecdsa" "golang.org/x/exp/slices" - "math/big" - "math/rand" - "reflect" - "testing" - "time" "github.com/keep-network/keep-core/internal/testutils" ) @@ -1185,8 +1186,7 @@ func newMockCoordinationProposalGenerator( } func (mcpg *mockCoordinationProposalGenerator) Generate( - walletPublicKeyHash [20]byte, - actionsChecklist []WalletActionType, + request *CoordinationProposalRequest, ) (CoordinationProposal, error) { - return mcpg.delegate(walletPublicKeyHash, actionsChecklist) + return mcpg.delegate(request.WalletPublicKeyHash, request.ActionsChecklist) } diff --git a/pkg/tbtcpg/chain.go b/pkg/tbtcpg/chain.go index 958e54fee3..23114615d9 100644 --- a/pkg/tbtcpg/chain.go +++ b/pkg/tbtcpg/chain.go @@ -1,30 +1,19 @@ package tbtcpg import ( - "github.com/keep-network/keep-core/pkg/bitcoin" - "github.com/keep-network/keep-core/pkg/chain" "math/big" "time" + "github.com/keep-network/keep-core/pkg/bitcoin" + "github.com/keep-network/keep-core/pkg/chain" + "github.com/keep-network/keep-core/pkg/tbtc" ) // Chain represents the interface that the wallet maintainer module expects // to interact with the anchoring blockchain on. type Chain interface { - // GetDepositRequest gets the on-chain deposit request for the given - // funding transaction hash and output index.The returned values represent: - // - deposit request which is non-nil only when the deposit request was - // found, - // - boolean value which is true if the deposit request was found, false - // otherwise, - // - error which is non-nil only when the function execution failed. It will - // be nil if the deposit request was not found, but the function execution - // succeeded. - GetDepositRequest( - fundingTxHash bitcoin.Hash, - fundingOutputIndex uint32, - ) (*tbtc.DepositChainRequest, bool, error) + tbtc.BridgeChain // PastNewWalletRegisteredEvents fetches past new wallet registered events // according to the provided filter or unfiltered if the filter is nil. Returned @@ -86,22 +75,6 @@ type Chain interface { // a processing. GetRedemptionRequestMinAge() (uint32, error) - // PastDepositRevealedEvents fetches past deposit reveal events according - // to the provided filter or unfiltered if the filter is nil. Returned - // events are sorted by the block number in the ascending order, i.e. the - // latest event is at the end of the slice. - PastDepositRevealedEvents( - filter *tbtc.DepositRevealedEventFilter, - ) ([]*tbtc.DepositRevealedEvent, error) - - // GetPendingRedemptionRequest gets the on-chain pending redemption request - // for the given wallet public key hash and redeemer output script. - // The returned bool value indicates whether the request was found or not. - GetPendingRedemptionRequest( - walletPublicKeyHash [20]byte, - redeemerOutputScript bitcoin.Script, - ) (*tbtc.RedemptionRequest, bool, error) - // ValidateDepositSweepProposal validates the given deposit sweep proposal // against the chain. It requires some additional data about the deposits // that must be fetched externally. Returns an error if the proposal is diff --git a/pkg/tbtcpg/chain_test.go b/pkg/tbtcpg/chain_test.go index 1d0fafed15..b1e09bc9f6 100644 --- a/pkg/tbtcpg/chain_test.go +++ b/pkg/tbtcpg/chain_test.go @@ -664,6 +664,17 @@ func (lc *LocalChain) SetAverageBlockTime(averageBlockTime time.Duration) { lc.averageBlockTime = averageBlockTime } +func (lc *LocalChain) GetWallet(walletPublicKeyHash [20]byte) ( + *tbtc.WalletChainData, + error, +) { + panic("unsupported") +} + +func (lc *LocalChain) ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte { + panic("unsupported") +} + type MockBlockCounter struct { mutex sync.Mutex currentBlock uint64 diff --git a/pkg/tbtcpg/deposit_sweep.go b/pkg/tbtcpg/deposit_sweep.go index 8df52b76d5..771e0c5934 100644 --- a/pkg/tbtcpg/deposit_sweep.go +++ b/pkg/tbtcpg/deposit_sweep.go @@ -2,12 +2,13 @@ package tbtcpg import ( "fmt" - "github.com/ipfs/go-log/v2" - "go.uber.org/zap" "math" "math/big" "sort" + "github.com/ipfs/go-log/v2" + "go.uber.org/zap" + "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" @@ -31,11 +32,13 @@ func NewDepositSweepTask( } } -func (dst *DepositSweepTask) Run(walletPublicKeyHash [20]byte) ( +func (dst *DepositSweepTask) Run(request *tbtc.CoordinationProposalRequest) ( tbtc.CoordinationProposal, bool, error, ) { + walletPublicKeyHash := request.WalletPublicKeyHash + taskLogger := logger.With( zap.String("task", dst.ActionType().String()), zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), diff --git a/pkg/tbtcpg/heartbeat.go b/pkg/tbtcpg/heartbeat.go index 2d00e51603..7f4bf36d67 100644 --- a/pkg/tbtcpg/heartbeat.go +++ b/pkg/tbtcpg/heartbeat.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/binary" "fmt" + "github.com/keep-network/keep-core/pkg/tbtc" ) @@ -18,11 +19,13 @@ func NewHeartbeatTask(chain Chain) *HeartbeatTask { } } -func (ht *HeartbeatTask) Run(walletPublicKeyHash [20]byte) ( +func (ht *HeartbeatTask) Run(request *tbtc.CoordinationProposalRequest) ( tbtc.CoordinationProposal, bool, error, ) { + walletPublicKeyHash := request.WalletPublicKeyHash + blockCounter, err := ht.chain.BlockCounter() if err != nil { return nil, false, fmt.Errorf("failed to get block counter: [%v]", err) diff --git a/pkg/tbtcpg/heartbeat_test.go b/pkg/tbtcpg/heartbeat_test.go index a8abff67d8..8970510ae5 100644 --- a/pkg/tbtcpg/heartbeat_test.go +++ b/pkg/tbtcpg/heartbeat_test.go @@ -2,10 +2,11 @@ package tbtcpg import ( "fmt" - "github.com/keep-network/keep-core/internal/testutils" - "github.com/keep-network/keep-core/pkg/tbtc" "reflect" "testing" + + "github.com/keep-network/keep-core/internal/testutils" + "github.com/keep-network/keep-core/pkg/tbtc" ) func TestHeartbeatTask_Run(t *testing.T) { @@ -58,7 +59,12 @@ func TestHeartbeatTask_Run(t *testing.T) { task := NewHeartbeatTask(tbtcChain) - proposal, ok, err := task.Run(walletPublicKeyHash) + proposal, ok, err := task.Run( + &tbtc.CoordinationProposalRequest{ + // Set only relevant fields. + WalletPublicKeyHash: walletPublicKeyHash, + }, + ) if !reflect.DeepEqual(test.expectedErr, err) { t.Errorf( diff --git a/pkg/tbtcpg/redemptions.go b/pkg/tbtcpg/redemptions.go index 9cfb617db2..b1a993dcb0 100644 --- a/pkg/tbtcpg/redemptions.go +++ b/pkg/tbtcpg/redemptions.go @@ -2,12 +2,13 @@ package tbtcpg import ( "fmt" - "github.com/ipfs/go-log/v2" - "go.uber.org/zap" "math/big" "sort" "time" + "github.com/ipfs/go-log/v2" + "go.uber.org/zap" + "github.com/keep-network/keep-core/internal/hexutils" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" @@ -29,11 +30,13 @@ func NewRedemptionTask( } } -func (rt *RedemptionTask) Run(walletPublicKeyHash [20]byte) ( +func (rt *RedemptionTask) Run(request *tbtc.CoordinationProposalRequest) ( tbtc.CoordinationProposal, bool, error, ) { + walletPublicKeyHash := request.WalletPublicKeyHash + taskLogger := logger.With( zap.String("task", rt.ActionType().String()), zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), diff --git a/pkg/tbtcpg/tbtcpg.go b/pkg/tbtcpg/tbtcpg.go index 9958997732..24d5434b3e 100644 --- a/pkg/tbtcpg/tbtcpg.go +++ b/pkg/tbtcpg/tbtcpg.go @@ -2,6 +2,7 @@ package tbtcpg import ( "fmt" + "github.com/ipfs/go-log/v2" "github.com/keep-network/keep-core/pkg/bitcoin" "github.com/keep-network/keep-core/pkg/tbtc" @@ -16,7 +17,10 @@ var logger = log.Logger("keep-tbtcpg") type ProposalTask interface { // Run executes the task and returns a proposal, a boolean flag indicating // whether the proposal was generated and an error if any. - Run(walletPublicKeyHash [20]byte) (tbtc.CoordinationProposal, bool, error) + Run( + request *tbtc.CoordinationProposalRequest, + ) (tbtc.CoordinationProposal, bool, error) + // ActionType returns the type of the action proposal. ActionType() tbtc.WalletActionType } @@ -53,19 +57,21 @@ func NewProposalGenerator( // given wallet's state. If none of the actions are valid, the generator // returns a no-op proposal. func (pg *ProposalGenerator) Generate( - walletPublicKeyHash [20]byte, - actionsChecklist []tbtc.WalletActionType, + request *tbtc.CoordinationProposalRequest, ) (tbtc.CoordinationProposal, error) { walletLogger := logger.With( - zap.String("walletPKH", fmt.Sprintf("0x%x", walletPublicKeyHash)), + zap.String( + "walletPKH", + fmt.Sprintf("0x%x", request.WalletPublicKeyHash), + ), ) walletLogger.Info( "starting proposal generation with tasks checklist [%v]", - actionsChecklist, + request.ActionsChecklist, ) - for _, action := range actionsChecklist { + for _, action := range request.ActionsChecklist { walletLogger.Infof("starting proposal task [%s]", action) taskIndex := slices.IndexFunc(pg.tasks, func(task ProposalTask) bool { @@ -77,7 +83,7 @@ func (pg *ProposalGenerator) Generate( continue } - proposal, ok, err := pg.tasks[taskIndex].Run(walletPublicKeyHash) + proposal, ok, err := pg.tasks[taskIndex].Run(request) if err != nil { return nil, fmt.Errorf( "error while running proposal task [%s]: [%v]", diff --git a/pkg/tbtcpg/tbtcpg_test.go b/pkg/tbtcpg/tbtcpg_test.go index b90cf238c4..675537ccdd 100644 --- a/pkg/tbtcpg/tbtcpg_test.go +++ b/pkg/tbtcpg/tbtcpg_test.go @@ -2,9 +2,10 @@ package tbtcpg import ( "fmt" - "github.com/keep-network/keep-core/pkg/tbtc" "reflect" "testing" + + "github.com/keep-network/keep-core/pkg/tbtc" ) func TestProposalGenerator_Generate(t *testing.T) { @@ -131,8 +132,11 @@ func TestProposalGenerator_Generate(t *testing.T) { } proposal, err := generator.Generate( - walletPublicKeyHash, - test.actionsChecklist, + &tbtc.CoordinationProposalRequest{ + WalletPublicKeyHash: walletPublicKeyHash, + WalletOperators: nil, + ActionsChecklist: test.actionsChecklist, + }, ) if !reflect.DeepEqual(test.expectedErr, err) { @@ -167,12 +171,14 @@ type mockProposalTask struct { results map[[20]byte]mockProposalTaskResult } -func (mpt *mockProposalTask) Run(walletPublicKeyHash [20]byte) ( +func (mpt *mockProposalTask) Run( + request *tbtc.CoordinationProposalRequest, +) ( tbtc.CoordinationProposal, bool, error, ) { - result, ok := mpt.results[walletPublicKeyHash] + result, ok := mpt.results[request.WalletPublicKeyHash] if !ok { panic("unexpected wallet public key hash") } From e5b0a7636a77e0d691a502f875bc0c2687e0d35d Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 4 Dec 2023 17:08:20 +0100 Subject: [PATCH 14/14] Fix active phase context lifetime --- pkg/tbtc/coordination.go | 21 ++++++++++++++++++--- pkg/tbtc/coordination_test.go | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/tbtc/coordination.go b/pkg/tbtc/coordination.go index e267106dcc..d4dc1b2829 100644 --- a/pkg/tbtc/coordination.go +++ b/pkg/tbtc/coordination.go @@ -375,14 +375,22 @@ func (ce *coordinationExecutor) coordinate( execLogger.Info("actions checklist is: [%v]", actionsChecklist) - // Set up a context that is cancelled when the active phase of the - // coordination window ends. + // Set up a context that is automatically cancelled when the active phase + // of the coordination window ends. + // + // The coordination leader keeps that context active for the lifetime of the + // active phase to provide retransmissions of the coordination message thus + // maximize the chance that all followers receive it on time. The only case + // when the leader cancels the context prematurely is when the leader's + // routine fails. + // + // The coordination follower cancels the context as soon as it receives + // the coordination message. ctx, cancelCtx := withCancelOnBlock( context.Background(), window.activePhaseEndBlock(), ce.waitForBlockFn, ) - defer cancelCtx() var proposal CoordinationProposal var faults []*coordinationFault @@ -396,6 +404,10 @@ func (ce *coordinationExecutor) coordinate( actionsChecklist, ) if err != nil { + // Cancel the context upon leader's routine failure. There is + // no point to keep the context active as retransmissions do not + // occur anyway. + cancelCtx() return nil, fmt.Errorf( "failed to execute leader's routine: [%v]", err, @@ -406,6 +418,9 @@ func (ce *coordinationExecutor) coordinate( } else { execLogger.Info("executing follower's routine") + // Cancel the context upon follower's routine completion. + defer cancelCtx() + proposal, faults, err = ce.executeFollowerRoutine( ctx, leader, diff --git a/pkg/tbtc/coordination_test.go b/pkg/tbtc/coordination_test.go index 729bc55fbf..1fccf8aac7 100644 --- a/pkg/tbtc/coordination_test.go +++ b/pkg/tbtc/coordination_test.go @@ -1118,7 +1118,7 @@ func TestCoordinationExecutor_ExecuteFollowerRoutine_WithIdleLeader(t *testing.T provider := netlocal.Connect() - broadcastChannel, err := provider.BroadcastChannelFor("test") + broadcastChannel, err := provider.BroadcastChannelFor("test-idle") if err != nil { t.Fatal(err) }