Skip to content

Commit

Permalink
Harden moving funds against edge cases (#3813)
Browse files Browse the repository at this point in the history
#Refs #3812.
This PR modifies the safety margin validation process used during moving
funds.
It is possible that a wallet may receive deposits just before it changes
states to `MovingFunds`.
It is also possible another wallets in `MovingFunds` state may commit to
transfer their funds to it.
To avoid a situation where a wallet ends up with additional funds after
it has already moved their own funds we must apply a safety margin.
In #3810 we already added
a 24-hour safety margin.
In this PR we add a longer 14-days safety margin when the wallet is a
target of a moving funds process from another wallet. We also make sure
the calculated safety margin is not greater than half of the
`movingFundsTimeout`, so that a wallet has enough time to finish their
moving funds process.
  • Loading branch information
lukasz-zimnoch committed May 6, 2024
2 parents 8c2fc66 + 506d878 commit a3904dd
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 42 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
171 changes: 158 additions & 13 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ import (

const localChainOperatorID = chain.OperatorID(1)

type movingFundsParameters = struct {
txMaxTotalFee uint64
dustThreshold uint64
timeoutResetDelay uint32
timeout uint32
timeoutSlashingAmount *big.Int
timeoutNotifierRewardMultiplier uint32
commitmentGasOffset uint16
sweepTxMaxTotalFee uint64
sweepTimeout uint32
sweepTimeoutSlashingAmount *big.Int
sweepTimeoutNotifierRewardMultiplier uint32
}

type localChain struct {
dkgResultSubmissionHandlersMutex sync.Mutex
dkgResultSubmissionHandlers map[int]func(submission *DKGResultSubmittedEvent)
Expand Down Expand Up @@ -57,6 +71,9 @@ type localChain struct {
pastDepositRevealedEventsMutex sync.Mutex
pastDepositRevealedEvents map[[32]byte][]*DepositRevealedEvent

pastMovingFundsCommitmentSubmittedEventsMutex sync.Mutex
pastMovingFundsCommitmentSubmittedEvents map[[32]byte][]*MovingFundsCommitmentSubmittedEvent

depositSweepProposalValidationsMutex sync.Mutex
depositSweepProposalValidations map[[32]byte]bool

Expand All @@ -81,6 +98,9 @@ type localChain struct {
depositRequestsMutex sync.Mutex
depositRequests map[[32]byte]*DepositChainRequest

movingFundsParametersMutex sync.Mutex
movingFundsParameters movingFundsParameters

blockCounter chain.BlockCounter
operatorPrivateKey *operator.PrivateKey
}
Expand Down Expand Up @@ -1099,6 +1119,130 @@ 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,
) {
lc.movingFundsParametersMutex.Lock()
defer lc.movingFundsParametersMutex.Unlock()

return lc.movingFundsParameters.txMaxTotalFee,
lc.movingFundsParameters.dustThreshold,
lc.movingFundsParameters.timeoutResetDelay,
lc.movingFundsParameters.timeout,
lc.movingFundsParameters.timeoutSlashingAmount,
lc.movingFundsParameters.timeoutNotifierRewardMultiplier,
lc.movingFundsParameters.commitmentGasOffset,
lc.movingFundsParameters.sweepTxMaxTotalFee,
lc.movingFundsParameters.sweepTimeout,
lc.movingFundsParameters.sweepTimeoutSlashingAmount,
lc.movingFundsParameters.sweepTimeoutNotifierRewardMultiplier,
nil
}

func (lc *localChain) SetMovingFundsParameters(
txMaxTotalFee uint64,
dustThreshold uint64,
timeoutResetDelay uint32,
timeout uint32,
timeoutSlashingAmount *big.Int,
timeoutNotifierRewardMultiplier uint32,
commitmentGasOffset uint16,
sweepTxMaxTotalFee uint64,
sweepTimeout uint32,
sweepTimeoutSlashingAmount *big.Int,
sweepTimeoutNotifierRewardMultiplier uint32,
) {
lc.movingFundsParametersMutex.Lock()
defer lc.movingFundsParametersMutex.Unlock()

lc.movingFundsParameters = movingFundsParameters{
txMaxTotalFee: txMaxTotalFee,
dustThreshold: dustThreshold,
timeoutResetDelay: timeoutResetDelay,
timeout: timeout,
timeoutSlashingAmount: timeoutSlashingAmount,
timeoutNotifierRewardMultiplier: timeoutNotifierRewardMultiplier,
commitmentGasOffset: commitmentGasOffset,
sweepTxMaxTotalFee: sweepTxMaxTotalFee,
sweepTimeout: sweepTimeout,
sweepTimeoutSlashingAmount: sweepTimeoutSlashingAmount,
sweepTimeoutNotifierRewardMultiplier: sweepTimeoutNotifierRewardMultiplier,
}
}

func (lc *localChain) PastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([]*MovingFundsCommitmentSubmittedEvent, error) {
lc.pastMovingFundsCommitmentSubmittedEventsMutex.Lock()
defer lc.pastMovingFundsCommitmentSubmittedEventsMutex.Unlock()

eventsKey, err := buildPastMovingFundsCommitmentSubmittedEventsKey(filter)
if err != nil {
return nil, err
}

events, ok := lc.pastMovingFundsCommitmentSubmittedEvents[eventsKey]
if !ok {
return nil, fmt.Errorf("no events for given filter")
}

return events, nil
}

func (lc *localChain) setPastMovingFundsCommitmentSubmittedEvents(
filter *MovingFundsCommitmentSubmittedEventFilter,
events []*MovingFundsCommitmentSubmittedEvent,
) error {
lc.pastMovingFundsCommitmentSubmittedEventsMutex.Lock()
defer lc.pastMovingFundsCommitmentSubmittedEventsMutex.Unlock()

eventsKey, err := buildPastMovingFundsCommitmentSubmittedEventsKey(filter)
if err != nil {
return err
}

lc.pastMovingFundsCommitmentSubmittedEvents[eventsKey] = events

return nil
}

func buildPastMovingFundsCommitmentSubmittedEventsKey(
filter *MovingFundsCommitmentSubmittedEventFilter,
) ([32]byte, error) {
if filter == nil {
return [32]byte{}, nil
}

var buffer bytes.Buffer

startBlock := make([]byte, 8)
binary.BigEndian.PutUint64(startBlock, filter.StartBlock)
buffer.Write(startBlock)

if filter.EndBlock != nil {
endBlock := make([]byte, 8)
binary.BigEndian.PutUint64(startBlock, *filter.EndBlock)
buffer.Write(endBlock)
}

for _, walletPublicKeyHash := range filter.WalletPublicKeyHash {
buffer.Write(walletPublicKeyHash[:])
}

return sha256.Sum256(buffer.Bytes()), nil
}

// Connect sets up the local chain.
func Connect(blockTime ...time.Duration) *localChain {
operatorPrivateKey, _, err := operator.GenerateKeyPair(local_v1.DefaultCurve)
Expand Down Expand Up @@ -1127,19 +1271,20 @@ func ConnectWithKey(
dkgResultChallengeHandlers: make(
map[int]func(submission *DKGResultChallengedEvent),
),
wallets: make(map[[20]byte]*WalletChainData),
blocksByTimestamp: make(map[uint64]uint64),
blocksHashesByNumber: make(map[uint64][32]byte),
pastDepositRevealedEvents: make(map[[32]byte][]*DepositRevealedEvent),
depositSweepProposalValidations: make(map[[32]byte]bool),
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
redemptionProposalValidations: make(map[[32]byte]bool),
movingFundsProposalValidations: make(map[[32]byte]bool),
movedFundsSweepProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
depositRequests: make(map[[32]byte]*DepositChainRequest),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
wallets: make(map[[20]byte]*WalletChainData),
blocksByTimestamp: make(map[uint64]uint64),
blocksHashesByNumber: make(map[uint64][32]byte),
pastDepositRevealedEvents: make(map[[32]byte][]*DepositRevealedEvent),
pastMovingFundsCommitmentSubmittedEvents: make(map[[32]byte][]*MovingFundsCommitmentSubmittedEvent),
depositSweepProposalValidations: make(map[[32]byte]bool),
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
redemptionProposalValidations: make(map[[32]byte]bool),
movingFundsProposalValidations: make(map[[32]byte]bool),
movedFundsSweepProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
depositRequests: make(map[[32]byte]*DepositChainRequest),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
}

return localChain
Expand Down
Loading

0 comments on commit a3904dd

Please sign in to comment.