Skip to content

Commit

Permalink
Added moved funds sweep action
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed Mar 1, 2024
1 parent fa18161 commit 7006e61
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 5 deletions.
99 changes: 95 additions & 4 deletions pkg/tbtc/moved_funds_sweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"math/big"
"time"

"github.com/keep-network/keep-core/pkg/bitcoin"
"go.uber.org/zap"

"github.com/ipfs/go-log/v2"
)

Expand All @@ -27,12 +30,37 @@ type MovedFundsSweepRequest struct {
}

const (
// movedFundsSweepProposalValidityBlocks determines the moving funds
// movedFundsSweepProposalValidityBlocks determines the moved funds sweep
// proposal validity time expressed in blocks. In other words, this is the
// worst-case time for a moving funds during which the wallet is busy and
// cannot take another actions. The value of 600 blocks is roughly 2 hours,
// assuming 12 seconds per block.
// worst-case time for a moved funds sweep during which the wallet is busy
// and cannot take another actions. The value of 600 blocks is roughly
// 2 hours, assuming 12 seconds per block.
movedFundsSweepProposalValidityBlocks = 600
// movedFundsSweepSigningTimeoutSafetyMarginBlocks determines the duration of
// the safety margin that must be preserved between the signing timeout and
// the timeout of the entire moved funds sweep action. This safety margin
// prevents against the case where signing completes late and there is not
// enough time to broadcast the moved funds sweep transaction properly.
// In such a case, wallet signatures may leak and make the wallet subject
// of fraud accusations. Usage of the safety margin ensures there is enough
// time to perform post-signing steps of the moved funds sweep action.
// The value of 300 blocks is roughly 1 hour, assuming 12 seconds per block.
movedFundsSweepSigningTimeoutSafetyMarginBlocks = 300
// movedFundsSweepBroadcastTimeout determines the time window for moved
// funds sweep transaction broadcast. It is guaranteed that at least
// movedFundsSweepSigningTimeoutSafetyMarginBlocks is preserved for the
// broadcast step. However, the happy path for the broadcast step is usually
// quick and few retries are needed to recover from temporary problems. That
// said, if the broadcast step does not succeed in a tight timeframe, there
// is no point to retry for the entire possible time window. Hence, the
// timeout for broadcast step is set as 25% of the entire time widow
// determined by movedFundsSweepSigningTimeoutSafetyMarginBlocks.
movedFundsSweepBroadcastTimeout = 15 * time.Minute
// movedFundsSweepBroadcastCheckDelay determines the delay that must
// be preserved between transaction broadcast and the check that ensures
// the transaction is known on the Bitcoin chain. This delay is needed
// as spreading the transaction over the Bitcoin network takes time.
movedFundsSweepBroadcastCheckDelay = 1 * time.Minute
)

// MovedFundsSweepProposal represents a moved funds sweep proposal issued by a
Expand All @@ -51,6 +79,69 @@ func (mfsp *MovedFundsSweepProposal) ValidityBlocks() uint64 {
return movedFundsSweepProposalValidityBlocks
}

type movedFundsSweepAction struct {
logger *zap.SugaredLogger
chain Chain
btcChain bitcoin.Chain

movedFundsSweepWallet wallet
transactionExecutor *walletTransactionExecutor

proposal *MovedFundsSweepProposal
proposalProcessingStartBlock uint64
proposalExpiryBlock uint64

signingTimeoutSafetyMarginBlocks uint64
broadcastTimeout time.Duration
broadcastCheckDelay time.Duration
}

func newMovedFundsSweepAction(
logger *zap.SugaredLogger,
chain Chain,
btcChain bitcoin.Chain,
movedFundsSweepWallet wallet,
signingExecutor walletSigningExecutor,
proposal *MovedFundsSweepProposal,
proposalProcessingStartBlock uint64,
proposalExpiryBlock uint64,
waitForBlockFn waitForBlockFn,
) *movedFundsSweepAction {
transactionExecutor := newWalletTransactionExecutor(
btcChain,
movedFundsSweepWallet,
signingExecutor,
waitForBlockFn,
)

return &movedFundsSweepAction{
logger: logger,
chain: chain,
btcChain: btcChain,
movedFundsSweepWallet: movedFundsSweepWallet,
transactionExecutor: transactionExecutor,
proposal: proposal,
proposalProcessingStartBlock: proposalProcessingStartBlock,
proposalExpiryBlock: proposalExpiryBlock,
signingTimeoutSafetyMarginBlocks: movedFundsSweepSigningTimeoutSafetyMarginBlocks,
broadcastTimeout: movedFundsSweepBroadcastTimeout,
broadcastCheckDelay: movedFundsSweepBroadcastCheckDelay,
}
}

func (mfsa *movedFundsSweepAction) execute() error {
// TODO: Implement
return nil
}

func (mfsa *movedFundsSweepAction) wallet() wallet {
return mfsa.movedFundsSweepWallet
}

func (mfsa *movedFundsSweepAction) actionType() WalletActionType {
return ActionMovedFundsSweep
}

// ValidateMovedFundsSweepProposal checks the moved funds sweep proposal with
// on-chain validation rules.
func ValidateMovedFundsSweepProposal(
Expand Down
59 changes: 58 additions & 1 deletion pkg/tbtc/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,64 @@ func (n *node) handleMovedFundsSweepProposal(
startBlock uint64,
expiryBlock uint64,
) {
// TODO: Implement
walletPublicKeyBytes, err := marshalPublicKey(wallet.publicKey)
if err != nil {
logger.Errorf("cannot marshal wallet public key: [%v]", err)
return
}

signingExecutor, ok, err := n.getSigningExecutor(wallet.publicKey)
if err != nil {
logger.Errorf("cannot get signing executor: [%v]", err)
return
}
// This check is actually redundant. We know the node controls some
// wallet signers as we just got the wallet from the registry using their
// public key hash. However, we are doing it just in case. The API
// contract of getSigningExecutor may change one day.
if !ok {
logger.Infof(
"node does not control signers of wallet PKH [0x%x]; "+
"ignoring the received moved funds sweep proposal",
walletPublicKeyBytes,
)
return
}

logger.Infof(
"starting orchestration of the moved funds sweep action for wallet "+
"[0x%x]; 20-byte public key hash of that wallet is [0x%x]",
walletPublicKeyBytes,
bitcoin.PublicKeyHash(wallet.publicKey),
)

walletActionLogger := logger.With(
zap.String("wallet", fmt.Sprintf("0x%x", walletPublicKeyBytes)),
zap.String("action", ActionMovedFundsSweep.String()),
zap.Uint64("startBlock", startBlock),
zap.Uint64("expiryBlock", expiryBlock),
)
walletActionLogger.Infof("dispatching wallet action")

action := newMovedFundsSweepAction(
walletActionLogger,
n.chain,
n.btcChain,
wallet,
signingExecutor,
proposal,
startBlock,
expiryBlock,
n.waitForBlockHeight,
)

err = n.walletDispatcher.dispatch(action)
if err != nil {
walletActionLogger.Errorf("cannot dispatch wallet action: [%v]", err)
return
}

walletActionLogger.Infof("wallet action dispatched successfully")
}

// coordinationLayerSettings represents settings for the coordination layer.
Expand Down

0 comments on commit 7006e61

Please sign in to comment.