9
9
10
10
"github.com/keep-network/keep-common/pkg/chain/ethereum"
11
11
"github.com/keep-network/keep-core/pkg/bitcoin"
12
+ "github.com/keep-network/keep-core/pkg/chain"
12
13
"go.uber.org/zap"
13
14
)
14
15
@@ -53,6 +54,11 @@ const (
53
54
movingFundsCommitmentConfirmationBlocks = 32
54
55
)
55
56
57
+ // MovingFundsCommitmentLookBackBlocks is the look-back period in blocks used
58
+ // when searching for submitted moving funds commitment events. It's equal to
59
+ // 30 days assuming 12 seconds per block.
60
+ const MovingFundsCommitmentLookBackBlocks = uint64 (216000 )
61
+
56
62
// MovingFundsProposal represents a moving funds proposal issued by a wallet's
57
63
// coordination leader.
58
64
type MovingFundsProposal struct {
@@ -312,20 +318,33 @@ func ValidateMovingFundsProposal(
312
318
proposal * MovingFundsProposal ,
313
319
) error
314
320
321
+ BlockCounter () (chain.BlockCounter , error )
322
+
315
323
GetWallet (walletPublicKeyHash [20 ]byte ) (* WalletChainData , error )
324
+
325
+ GetMovingFundsParameters () (
326
+ txMaxTotalFee uint64 ,
327
+ dustThreshold uint64 ,
328
+ timeoutResetDelay uint32 ,
329
+ timeout uint32 ,
330
+ timeoutSlashingAmount * big.Int ,
331
+ timeoutNotifierRewardMultiplier uint32 ,
332
+ commitmentGasOffset uint16 ,
333
+ sweepTxMaxTotalFee uint64 ,
334
+ sweepTimeout uint32 ,
335
+ sweepTimeoutSlashingAmount * big.Int ,
336
+ sweepTimeoutNotifierRewardMultiplier uint32 ,
337
+ err error ,
338
+ )
339
+
340
+ PastMovingFundsCommitmentSubmittedEvents (
341
+ filter * MovingFundsCommitmentSubmittedEventFilter ,
342
+ ) ([]* MovingFundsCommitmentSubmittedEvent , error )
316
343
},
317
344
) error {
318
345
validateProposalLogger .Infof ("calling chain for proposal validation" )
319
346
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 )
347
+ err := ValidateMovingFundsSafetyMargin (walletPublicKeyHash , chain )
329
348
if err != nil {
330
349
return fmt .Errorf ("moving funds proposal is invalid: [%v]" , err )
331
350
}
@@ -354,10 +373,87 @@ func ValidateMovingFundsProposal(
354
373
// deposits so, it makes sense to preserve a safety margin before moving
355
374
// funds to give the last minute deposits a chance to become eligible for
356
375
// deposit sweep.
376
+ //
377
+ // Similarly, wallets that just entered the MovingFunds state may have become
378
+ // target wallets for another moving funds wallets. It makes sense to preserve
379
+ // a safety margin to allow the wallet to merge the moved funds from another
380
+ // wallets. In this case a longer safety margin should be used.
357
381
func ValidateMovingFundsSafetyMargin (
358
- walletChainData * WalletChainData ,
382
+ walletPublicKeyHash [20 ]byte ,
383
+ chain interface {
384
+ BlockCounter () (chain.BlockCounter , error )
385
+
386
+ GetWallet (walletPublicKeyHash [20 ]byte ) (* WalletChainData , error )
387
+
388
+ GetMovingFundsParameters () (
389
+ txMaxTotalFee uint64 ,
390
+ dustThreshold uint64 ,
391
+ timeoutResetDelay uint32 ,
392
+ timeout uint32 ,
393
+ timeoutSlashingAmount * big.Int ,
394
+ timeoutNotifierRewardMultiplier uint32 ,
395
+ commitmentGasOffset uint16 ,
396
+ sweepTxMaxTotalFee uint64 ,
397
+ sweepTimeout uint32 ,
398
+ sweepTimeoutSlashingAmount * big.Int ,
399
+ sweepTimeoutNotifierRewardMultiplier uint32 ,
400
+ err error ,
401
+ )
402
+
403
+ PastMovingFundsCommitmentSubmittedEvents (
404
+ filter * MovingFundsCommitmentSubmittedEventFilter ,
405
+ ) ([]* MovingFundsCommitmentSubmittedEvent , error )
406
+ },
359
407
) error {
408
+ // In most cases the safety margin of 24 hours should be enough. It will
409
+ // allow the wallet to sweep the last deposits that were made before the
410
+ // wallet entered the moving funds state.
360
411
safetyMargin := time .Duration (24 ) * time .Hour
412
+
413
+ // It is possible that our wallet is the target wallet in another pending
414
+ // moving funds procedure. If this is the case we must apply a longer
415
+ // 14-day safety margin. This will ensure the funds moved from another
416
+ // wallet can be merged with our wallet's main UTXO before moving funds.
417
+ isMovingFundsTarget , err := isWalletPendingMovingFundsTarget (
418
+ walletPublicKeyHash ,
419
+ chain ,
420
+ )
421
+ if err != nil {
422
+ return fmt .Errorf (
423
+ "cannot check if wallet is pending moving funds target: [%w]" ,
424
+ err ,
425
+ )
426
+ }
427
+
428
+ if isMovingFundsTarget {
429
+ safetyMargin = time .Duration (24 ) * 14 * time .Hour
430
+ }
431
+
432
+ // As the moving funds procedure is time constrained, we must ensure the
433
+ // safety margin does not exceed half of the moving funds timeout parameter.
434
+ // This should give the wallet enough time to complete moving funds.
435
+ _ , _ , _ , movingFundsTimeout , _ , _ , _ , _ , _ , _ , _ , err :=
436
+ chain .GetMovingFundsParameters ()
437
+ if err != nil {
438
+ return fmt .Errorf ("cannot get moving funds parameters: [%w]" , err )
439
+ }
440
+
441
+ maxAllowedSafetyMargin := time .Duration (
442
+ float64 (movingFundsTimeout ) * 0.5 * float64 (time .Second ),
443
+ )
444
+
445
+ if safetyMargin > maxAllowedSafetyMargin {
446
+ safetyMargin = maxAllowedSafetyMargin
447
+ }
448
+
449
+ walletChainData , err := chain .GetWallet (walletPublicKeyHash )
450
+ if err != nil {
451
+ return fmt .Errorf (
452
+ "cannot get wallet's chain data: [%w]" ,
453
+ err ,
454
+ )
455
+ }
456
+
361
457
safetyMarginExpiresAt := walletChainData .MovingFundsRequestedAt .Add (safetyMargin )
362
458
363
459
if time .Now ().Before (safetyMarginExpiresAt ) {
@@ -375,6 +471,97 @@ func (mfa *movingFundsAction) actionType() WalletActionType {
375
471
return ActionMovingFunds
376
472
}
377
473
474
+ func isWalletPendingMovingFundsTarget (
475
+ walletPublicKeyHash [20 ]byte ,
476
+
477
+ chain interface {
478
+ BlockCounter () (chain.BlockCounter , error )
479
+
480
+ GetWallet (walletPublicKeyHash [20 ]byte ) (* WalletChainData , error )
481
+
482
+ GetMovingFundsParameters () (
483
+ txMaxTotalFee uint64 ,
484
+ dustThreshold uint64 ,
485
+ timeoutResetDelay uint32 ,
486
+ timeout uint32 ,
487
+ timeoutSlashingAmount * big.Int ,
488
+ timeoutNotifierRewardMultiplier uint32 ,
489
+ commitmentGasOffset uint16 ,
490
+ sweepTxMaxTotalFee uint64 ,
491
+ sweepTimeout uint32 ,
492
+ sweepTimeoutSlashingAmount * big.Int ,
493
+ sweepTimeoutNotifierRewardMultiplier uint32 ,
494
+ err error ,
495
+ )
496
+
497
+ PastMovingFundsCommitmentSubmittedEvents (
498
+ filter * MovingFundsCommitmentSubmittedEventFilter ,
499
+ ) ([]* MovingFundsCommitmentSubmittedEvent , error )
500
+ },
501
+ ) (bool , error ) {
502
+ blockCounter , err := chain .BlockCounter ()
503
+ if err != nil {
504
+ return false , fmt .Errorf ("failed to get block counter: [%w]" , err )
505
+ }
506
+
507
+ currentBlockNumber , err := blockCounter .CurrentBlock ()
508
+ if err != nil {
509
+ return false , fmt .Errorf (
510
+ "failed to get current block number: [%w]" ,
511
+ err ,
512
+ )
513
+ }
514
+
515
+ filterStartBlock := uint64 (0 )
516
+ if currentBlockNumber > MovingFundsCommitmentLookBackBlocks {
517
+ filterStartBlock = currentBlockNumber - MovingFundsCommitmentLookBackBlocks
518
+ }
519
+
520
+ // Get all the recent moving funds commitment submitted events.
521
+ filter := & MovingFundsCommitmentSubmittedEventFilter {
522
+ StartBlock : filterStartBlock ,
523
+ }
524
+
525
+ events , err := chain .PastMovingFundsCommitmentSubmittedEvents (filter )
526
+ if err != nil {
527
+ return false , fmt .Errorf (
528
+ "failed to get past moving funds commitment submitted events: [%w]" ,
529
+ err ,
530
+ )
531
+ }
532
+
533
+ isWalletTarget := func (event * MovingFundsCommitmentSubmittedEvent ) bool {
534
+ for _ , targetWallet := range event .TargetWallets {
535
+ if walletPublicKeyHash == targetWallet {
536
+ return true
537
+ }
538
+ }
539
+ return false
540
+ }
541
+
542
+ for _ , event := range events {
543
+ if ! isWalletTarget (event ) {
544
+ continue
545
+ }
546
+
547
+ // Our wallet is on the list of target wallets. If the state is moving
548
+ // funds, there is probably moving funds to our wallet in the process.
549
+ walletChainData , err := chain .GetWallet (walletPublicKeyHash )
550
+ if err != nil {
551
+ return false , fmt .Errorf (
552
+ "cannot get wallet's chain data: [%w]" ,
553
+ err ,
554
+ )
555
+ }
556
+
557
+ if walletChainData .State == StateMovingFunds {
558
+ return true , nil
559
+ }
560
+ }
561
+
562
+ return false , nil
563
+ }
564
+
378
565
func assembleMovingFundsTransaction (
379
566
bitcoinChain bitcoin.Chain ,
380
567
walletMainUtxo * bitcoin.UnspentTransactionOutput ,
0 commit comments