@@ -313,19 +313,26 @@ func ValidateMovingFundsProposal(
313
313
) error
314
314
315
315
GetWallet (walletPublicKeyHash [20 ]byte ) (* WalletChainData , error )
316
+
317
+ GetMovingFundsParameters () (
318
+ txMaxTotalFee uint64 ,
319
+ dustThreshold uint64 ,
320
+ timeoutResetDelay uint32 ,
321
+ timeout uint32 ,
322
+ timeoutSlashingAmount * big.Int ,
323
+ timeoutNotifierRewardMultiplier uint32 ,
324
+ commitmentGasOffset uint16 ,
325
+ sweepTxMaxTotalFee uint64 ,
326
+ sweepTimeout uint32 ,
327
+ sweepTimeoutSlashingAmount * big.Int ,
328
+ sweepTimeoutNotifierRewardMultiplier uint32 ,
329
+ err error ,
330
+ )
316
331
},
317
332
) error {
318
333
validateProposalLogger .Infof ("calling chain for proposal validation" )
319
334
320
- walletChainData , err := chain .GetWallet (walletPublicKeyHash )
321
- if err != nil {
322
- return fmt .Errorf (
323
- "cannot get wallet's chain data: [%w]" ,
324
- err ,
325
- )
326
- }
327
-
328
- err = ValidateMovingFundsSafetyMargin (walletChainData )
335
+ err := ValidateMovingFundsSafetyMargin (walletPublicKeyHash , chain )
329
336
if err != nil {
330
337
return fmt .Errorf ("moving funds proposal is invalid: [%v]" , err )
331
338
}
@@ -354,10 +361,78 @@ func ValidateMovingFundsProposal(
354
361
// deposits so, it makes sense to preserve a safety margin before moving
355
362
// funds to give the last minute deposits a chance to become eligible for
356
363
// deposit sweep.
364
+ //
365
+ // Similarly, wallets that just entered the MovingFunds state may have become
366
+ // target wallets for another moving funds wallets. It makes sense to preserve
367
+ // a safety margin to allow the wallet to merge the moved funds from another
368
+ // wallets. In this case a longer safety margin should be used.
357
369
func ValidateMovingFundsSafetyMargin (
358
- walletChainData * WalletChainData ,
370
+ walletPublicKeyHash [20 ]byte ,
371
+ chain interface {
372
+ GetWallet (walletPublicKeyHash [20 ]byte ) (* WalletChainData , error )
373
+
374
+ GetMovingFundsParameters () (
375
+ txMaxTotalFee uint64 ,
376
+ dustThreshold uint64 ,
377
+ timeoutResetDelay uint32 ,
378
+ timeout uint32 ,
379
+ timeoutSlashingAmount * big.Int ,
380
+ timeoutNotifierRewardMultiplier uint32 ,
381
+ commitmentGasOffset uint16 ,
382
+ sweepTxMaxTotalFee uint64 ,
383
+ sweepTimeout uint32 ,
384
+ sweepTimeoutSlashingAmount * big.Int ,
385
+ sweepTimeoutNotifierRewardMultiplier uint32 ,
386
+ err error ,
387
+ )
388
+ },
359
389
) error {
390
+ // In most cases the safety margin of 24 hours should be enough. It will
391
+ // allow the wallet to sweep the last deposits that were made before the
392
+ // wallet entered the moving funds state.
360
393
safetyMargin := time .Duration (24 ) * time .Hour
394
+
395
+ // It is possible that our wallet is the target wallet in another pending
396
+ // moving funds procedure. If this is the case we must apply a longer
397
+ // 14-day safety margin. This will ensure the funds moved from another
398
+ // wallet can be merged with our wallet's main UTXO before moving funds.
399
+ isMovingFundsTarget , err := isWalletPendingMovingFundsTarget ()
400
+ if err != nil {
401
+ return fmt .Errorf (
402
+ "cannot check if wallet is pending moving funds target: [%w]" ,
403
+ err ,
404
+ )
405
+ }
406
+
407
+ if isMovingFundsTarget {
408
+ safetyMargin = time .Duration (24 ) * 14 * time .Hour
409
+ }
410
+
411
+ // As the moving funds procedure is time constrained, we must ensure the
412
+ // safety margin does not exceed half of the moving funds timeout parameter.
413
+ // This should give the wallet enough time to complete moving funds.
414
+ _ , _ , _ , movingFundsTimeout , _ , _ , _ , _ , _ , _ , _ , err :=
415
+ chain .GetMovingFundsParameters ()
416
+ if err != nil {
417
+ return fmt .Errorf ("cannot get moving funds parameters: [%w]" , err )
418
+ }
419
+
420
+ maxAllowedSafetyMargin := time .Duration (
421
+ float64 (movingFundsTimeout ) * 0.5 * float64 (time .Second ),
422
+ )
423
+
424
+ if safetyMargin > maxAllowedSafetyMargin {
425
+ safetyMargin = maxAllowedSafetyMargin
426
+ }
427
+
428
+ walletChainData , err := chain .GetWallet (walletPublicKeyHash )
429
+ if err != nil {
430
+ return fmt .Errorf (
431
+ "cannot get wallet's chain data: [%w]" ,
432
+ err ,
433
+ )
434
+ }
435
+
361
436
safetyMarginExpiresAt := walletChainData .MovingFundsRequestedAt .Add (safetyMargin )
362
437
363
438
if time .Now ().Before (safetyMarginExpiresAt ) {
@@ -375,6 +450,11 @@ func (mfa *movingFundsAction) actionType() WalletActionType {
375
450
return ActionMovingFunds
376
451
}
377
452
453
+ func isWalletPendingMovingFundsTarget () (bool , error ) {
454
+ // TODO: Implement
455
+ return false , nil
456
+ }
457
+
378
458
func assembleMovingFundsTransaction (
379
459
bitcoinChain bitcoin.Chain ,
380
460
walletMainUtxo * bitcoin.UnspentTransactionOutput ,
0 commit comments