Skip to content

Commit

Permalink
Adjusted safety margin calculations when moving funds
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed May 1, 2024
1 parent 8c2fc66 commit ec00a89
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 28 deletions.
26 changes: 26 additions & 0 deletions pkg/tbtc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,32 @@ type BridgeChain interface {
movingFundsTxHash bitcoin.Hash,
movingFundsTxOutpointIndex uint32,
) (*MovedFundsSweepRequest, bool, error)

// GetMovingFundsParameters gets the current value of parameters relevant
// for the moving funds process.
GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
)

// PastMovingFundsCommitmentSubmittedEvents fetches past moving funds
// commitment submitted 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.
PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error)
}

// NewWalletRegisteredEvent represents a new wallet registered event.
Expand Down
23 changes: 23 additions & 0 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,29 @@ func buildMovedFundsSweepProposalValidationKey(
return sha256.Sum256(buffer.Bytes()), nil
}

func (lc *localChain) GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
) {
panic("unsupported")
}

func (lc *localChain) PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error) {
panic("unsupported")
}

// Connect sets up the local chain.
func Connect(blockTime ...time.Duration) *localChain {
operatorPrivateKey, _, err := operator.GenerateKeyPair(local_v1.DefaultCurve)
Expand Down
207 changes: 197 additions & 10 deletions pkg/tbtc/moving_funds.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/keep-network/keep-common/pkg/chain/ethereum"
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/chain"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -53,6 +54,11 @@ const (
movingFundsCommitmentConfirmationBlocks = 32
)

// MovingFundsCommitmentLookBackBlocks is the look-back period in blocks used
// when searching for submitted moving funds commitment events. It's equal to
// 30 days assuming 12 seconds per block.
const MovingFundsCommitmentLookBackBlocks = uint64(216000)

// MovingFundsProposal represents a moving funds proposal issued by a wallet's
// coordination leader.
type MovingFundsProposal struct {
Expand Down Expand Up @@ -312,20 +318,33 @@ func ValidateMovingFundsProposal(
proposal *MovingFundsProposal,
) error

BlockCounter() (chain.BlockCounter, error)

GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error)

GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
)

PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error)
},
) error {
validateProposalLogger.Infof("calling chain for proposal validation")

walletChainData, err := chain.GetWallet(walletPublicKeyHash)
if err != nil {
return fmt.Errorf(
"cannot get wallet's chain data: [%w]",
err,
)
}

err = ValidateMovingFundsSafetyMargin(walletChainData)
err := ValidateMovingFundsSafetyMargin(walletPublicKeyHash, chain)
if err != nil {
return fmt.Errorf("moving funds proposal is invalid: [%v]", err)
}
Expand Down Expand Up @@ -354,10 +373,87 @@ func ValidateMovingFundsProposal(
// deposits so, it makes sense to preserve a safety margin before moving
// funds to give the last minute deposits a chance to become eligible for
// deposit sweep.
//
// Similarly, wallets that just entered the MovingFunds state may have become
// target wallets for another moving funds wallets. It makes sense to preserve
// a safety margin to allow the wallet to merge the moved funds from another
// wallets. In this case a longer safety margin should be used.
func ValidateMovingFundsSafetyMargin(
walletChainData *WalletChainData,
walletPublicKeyHash [20]byte,
chain interface {
BlockCounter() (chain.BlockCounter, error)

GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error)

GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
)

PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error)
},
) error {
// In most cases the safety margin of 24 hours should be enough. It will
// allow the wallet to sweep the last deposits that were made before the
// wallet entered the moving funds state.
safetyMargin := time.Duration(24) * time.Hour

// It is possible that our wallet is the target wallet in another pending
// moving funds procedure. If this is the case we must apply a longer
// 14-day safety margin. This will ensure the funds moved from another
// wallet can be merged with our wallet's main UTXO before moving funds.
isMovingFundsTarget, err := isWalletPendingMovingFundsTarget(
walletPublicKeyHash,
chain,
)
if err != nil {
return fmt.Errorf(
"cannot check if wallet is pending moving funds target: [%w]",
err,
)
}

if isMovingFundsTarget {
safetyMargin = time.Duration(24) * 14 * time.Hour
}

// As the moving funds procedure is time constrained, we must ensure the
// safety margin does not exceed half of the moving funds timeout parameter.
// This should give the wallet enough time to complete moving funds.
_, _, _, movingFundsTimeout, _, _, _, _, _, _, _, err :=
chain.GetMovingFundsParameters()
if err != nil {
return fmt.Errorf("cannot get moving funds parameters: [%w]", err)
}

maxAllowedSafetyMargin := time.Duration(
float64(movingFundsTimeout) * 0.5 * float64(time.Second),
)

if safetyMargin > maxAllowedSafetyMargin {
safetyMargin = maxAllowedSafetyMargin
}

walletChainData, err := chain.GetWallet(walletPublicKeyHash)
if err != nil {
return fmt.Errorf(
"cannot get wallet's chain data: [%w]",
err,
)
}

safetyMarginExpiresAt := walletChainData.MovingFundsRequestedAt.Add(safetyMargin)

if time.Now().Before(safetyMarginExpiresAt) {
Expand All @@ -375,6 +471,97 @@ func (mfa *movingFundsAction) actionType() WalletActionType {
return ActionMovingFunds
}

func isWalletPendingMovingFundsTarget(
walletPublicKeyHash [20]byte,

chain interface {
BlockCounter() (chain.BlockCounter, error)

GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error)

GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
)

PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error)
},
) (bool, error) {
blockCounter, err := chain.BlockCounter()
if err != nil {
return false, fmt.Errorf("failed to get block counter: [%w]", err)
}

currentBlockNumber, err := blockCounter.CurrentBlock()
if err != nil {
return false, fmt.Errorf(
"failed to get current block number: [%w]",
err,
)
}

filterStartBlock := uint64(0)
if currentBlockNumber > MovingFundsCommitmentLookBackBlocks {
filterStartBlock = currentBlockNumber - MovingFundsCommitmentLookBackBlocks
}

// Get all the recent moving funds commitment submitted events.
filter := &MovingFundsCommitmentSubmittedEventFilter{
StartBlock: filterStartBlock,
}

events, err := chain.PastMovingFundsCommitmentSubmittedEvents(filter)
if err != nil {
return false, fmt.Errorf(
"failed to get past moving funds commitment submitted events: [%w]",
err,
)
}

isWalletTarget := func(event *MovingFundsCommitmentSubmittedEvent) bool {
for _, targetWallet := range event.TargetWallets {
if walletPublicKeyHash == targetWallet {
return true
}
}
return false
}

for _, event := range events {
if !isWalletTarget(event) {
continue
}

// Our wallet is on the list of target wallets. If the state is moving
// funds, there is probably moving funds to our wallet in the process.
walletChainData, err := chain.GetWallet(walletPublicKeyHash)
if err != nil {
return false, fmt.Errorf(
"cannot get wallet's chain data: [%w]",
err,
)
}

if walletChainData.State == StateMovingFunds {
return true, nil
}
}

return false, nil
}

func assembleMovingFundsTransaction(
bitcoinChain bitcoin.Chain,
walletMainUtxo *bitcoin.UnspentTransactionOutput,
Expand Down
17 changes: 0 additions & 17 deletions pkg/tbtcpg/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,6 @@ type Chain interface {
proposal *tbtc.HeartbeatProposal,
) error

// GetMovingFundsParameters gets the current value of parameters relevant
// for the moving funds process.
GetMovingFundsParameters() (
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
err error,
)

// PastMovingFundsCommitmentSubmittedEvents fetches past moving funds
// commitment submitted events according to the provided filter or
// unfiltered if the filter is nil. Returned events are sorted by the block
Expand Down
2 changes: 1 addition & 1 deletion pkg/tbtcpg/moving_funds.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (mft *MovingFundsTask) Run(request *tbtc.CoordinationProposalRequest) (

// Check the safety margin for moving funds early. This will prevent
// commitment submission if the wallet is not safe to move funds.
err = tbtc.ValidateMovingFundsSafetyMargin(walletChainData)
err = tbtc.ValidateMovingFundsSafetyMargin(walletPublicKeyHash, mft.chain)
if err != nil {
taskLogger.Infof("source wallet moving funds safety margin validation failed: [%v]", err)
return nil, false, nil
Expand Down

0 comments on commit ec00a89

Please sign in to comment.