Skip to content

Commit

Permalink
Maintainer optimizations around redemptions (#3663)
Browse files Browse the repository at this point in the history
Here we introduce a bunch of optimizations to the wallet maintainer. All
of them are related to redemptions. I recommend reviewing commit by
commit. Here is a brief description of the changes.

### Improve past `RedemptionRequested` events filtering

The wallet maintainer seeks past `RedemptionRequested` events in order
to propose redemptions. So far, the events lookup was not constrained to
any block range which could be problematic for several Ethereum
providers. This can be improved as the wallet maintainer is only
interested in pending requests that are not timed out yet. That means
there is no sense to fetch events that were emitted before `now -
redemptionRequestTimeout` block. We reflect that by narrowing the block
range of the filter thus making the call more lightweight.

### Submit multiple redemption proposals for different wallets

Redemptions are time-sensitive so we must make sure all of them are
handled in a reasonable time. So far, the maintainer iterated from the
oldest wallet and proposed redemptions for the first wallet having
pending redemptions. We can do it better and propose redemptions for
multiple wallets at the same time. However, given the fact signing is
computationally heavy and a lot of operator nodes have shares in
multiple wallets, we need to set a reasonable limit on the maximum
number of wallets performing redemptions at the same time. We achieve
that by introducing the `RedemptionWalletsLimit` configurable parameter.

### Add an ability to limit the proposed redemptions value

We want to limit the risk for initial mainnet redemptions done shortly
after the redemptions release. To achieve that, we need a way to limit
the total amount of a single redemption proposal to a reasonable value.
We address that indirectly, by introducing the
`RedemptionRequestAmountLimit` for the wallet maintainer. This parameter
guarantees that the redemption proposals submitted by the maintainer
will consist of redemption requests whose amounts are below a certain
satoshi value.
Combined with `WalletCoordinator.redemptionMaxSize` on-chain parameter,
we obtain a tool to cap the total value of submitted redemption
proposals to safe levels. All redemption requests exceeding the
`RedemptionRequestAmountLimit` can be handled manually through the
`maintainer-cli` tool after confirming everything is all right.
  • Loading branch information
pdyraga authored Jul 4, 2023
2 parents 4d30473 + 56335a1 commit d76aaef
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 153 deletions.
14 changes: 14 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,20 @@ func initMaintainerFlags(command *cobra.Command, cfg *config.Config) {
"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",
Expand Down
16 changes: 16 additions & 0 deletions cmd/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@ var cmdFlagsTests = map[string]struct {
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",
Expand Down
44 changes: 24 additions & 20 deletions cmd/maintainercli.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,14 @@ var proposeRedemptionCommand = cobra.Command{
return fmt.Errorf("could not connect to Electrum chain: [%v]", err)
}

var walletPublicKeyHash [20]byte
var walletPublicKeyHashes [][20]byte
if len(wallet) > 0 {
var err error
walletPublicKeyHash, err = newWalletPublicKeyHash(wallet)
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 {
Expand All @@ -420,31 +421,34 @@ var proposeRedemptionCommand = cobra.Command{
}
}

walletPublicKeyHash, redemptions, err := walletmtr.FindPendingRedemptions(
walletsPendingRedemptions, err := walletmtr.FindPendingRedemptions(
tbtcChain,
walletPublicKeyHash,
redemptionMaxSize,
walletmtr.PendingRedemptionsFilter{
WalletPublicKeyHashes: walletPublicKeyHashes,
WalletsLimit: 1,
RequestsLimit: redemptionMaxSize,
RequestAmountLimit: 0,
},
)
if err != nil {
return fmt.Errorf("failed to prepare redemption proposal: %v", err)
return fmt.Errorf("failed to find pending redemption requests: [%w]", err)
}

if len(redemptions) > int(redemptionMaxSize) {
return fmt.Errorf(
"redemptions number [%d] is greater than redemptions max size [%d]",
len(redemptions),
redemptionMaxSize,
for walletPublicKeyHash, redeemersOutputScripts := range walletsPendingRedemptions {
err := walletmtr.ProposeRedemption(
tbtcChain,
btcChain,
walletPublicKeyHash,
fee,
redeemersOutputScripts,
dryRun,
)
if err != nil {
return err
}
}

return walletmtr.ProposeRedemption(
tbtcChain,
btcChain,
walletPublicKeyHash,
fee,
redemptions,
dryRun,
)
return nil
},
}

Expand Down
8 changes: 8 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ func TestReadConfigFromFile(t *testing.T) {
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,
Expand Down
4 changes: 4 additions & 0 deletions pkg/chain/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ func closerBlock(timestamp uint64, b1, b2 *types.Block) *types.Block {
return b2
}

func (bc *baseChain) AverageBlockTime() time.Duration {
return 12 * time.Second
}

// wrapClientAddons wraps the client instance with add-ons like logging, rate
// limiting and so on.
func wrapClientAddons(
Expand Down
5 changes: 5 additions & 0 deletions pkg/maintainer/wallet/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package wallet

import (
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/chain"
"math/big"
"time"

Expand Down Expand Up @@ -141,4 +142,8 @@ type Chain interface {
GetWalletLock(
walletPublicKeyHash [20]byte,
) (time.Time, tbtc.WalletActionType, error)

BlockCounter() (chain.BlockCounter, error)

AverageBlockTime() time.Duration
}
10 changes: 9 additions & 1 deletion pkg/maintainer/wallet/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"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"
)

Expand Down Expand Up @@ -458,3 +458,11 @@ func (lc *LocalChain) ResetWalletLock(
walletAction: tbtc.ActionNoop,
}
}

func (lc *LocalChain) BlockCounter() (chain.BlockCounter, error) {
panic("unsupported")
}

func (lc *LocalChain) AverageBlockTime() time.Duration {
panic("unsupported")
}
14 changes: 9 additions & 5 deletions pkg/maintainer/wallet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package wallet
import "time"

const (
DefaultRedemptionInterval = 3 * time.Hour
DefaultDepositSweepInterval = 48 * time.Hour
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
DepositSweepInterval time.Duration
Enabled bool
RedemptionInterval time.Duration
RedemptionWalletsLimit uint16
RedemptionRequestAmountLimit uint64
DepositSweepInterval time.Duration
}
Loading

0 comments on commit d76aaef

Please sign in to comment.