11import ldk from './ldk' ;
2- import { err , ok , Result } from './utils/result' ;
2+ import { Err , err , ok , Result } from './utils/result' ;
33import {
44 DefaultLdkDataShape ,
55 DefaultTransactionDataShape ,
@@ -61,6 +61,7 @@ import {
6161 findOutputsFromRawTxs ,
6262 parseData ,
6363 promiseTimeout ,
64+ sleep ,
6465 startParamCheck ,
6566} from './utils/helpers' ;
6667import * as bitcoin from 'bitcoinjs-lib' ;
@@ -135,6 +136,10 @@ class LightningManager {
135136 paymentFailedSubscription : EmitterSubscription | undefined ;
136137 paymentSentSubscription : EmitterSubscription | undefined ;
137138
139+ private isSyncing : boolean = false ;
140+ private forceSync : boolean = false ;
141+ private pendingSyncPromises : Array < ( result : Result < string > ) => void > = [ ] ;
142+
138143 constructor ( ) {
139144 // Step 0: Subscribe to all events
140145 ldk . onEvent ( EEventTypes . native_log , ( line ) => {
@@ -474,20 +479,62 @@ class LightningManager {
474479 /**
475480 * Fetches current best block and sends to LDK to update both channelManager and chainMonitor.
476481 * Also watches transactions and outputs for confirmed and unconfirmed transactions and updates LDK.
482+ * @param {number } [timeout] Timeout to set for each async function in this method. Potential overall timeout may be greater.
483+ * @param {number } [retryAttempts] Will attempt to sync LDK a given number of times before giving up.
484+ * @param {boolean } [force] In the event a sync is underway, this will force another sync once the current sync is complete.
477485 * @returns {Promise<Result<string>> }
478486 */
479- async syncLdk ( ) : Promise < Result < string > > {
487+ async syncLdk ( {
488+ timeout = 5000 ,
489+ retryAttempts = 1 ,
490+ force = false ,
491+ } : {
492+ timeout ?: number ;
493+ retryAttempts ?: number ;
494+ force ?: boolean ;
495+ } = { } ) : Promise < Result < string > > {
496+ // Check that the getBestBlock method has been provided.
480497 if ( ! this . getBestBlock ) {
481- return err ( 'No getBestBlock method provided.' ) ;
498+ return this . handleSyncError ( err ( 'No getBestBlock method provided.' ) ) ;
482499 }
483- const bestBlock = await this . getBestBlock ( ) ;
484- const height = bestBlock ?. height ;
485500
486- //Don't update unnecessarily
501+ if ( force && this . isSyncing && ! this . forceSync ) {
502+ // If syncing is already underway and force is true, set forceSync to true.
503+ this . forceSync = true ;
504+ }
505+ if ( this . isSyncing ) {
506+ // If isSyncing, push to pendingSyncPromises to resolve when the current sync completes.
507+ return new Promise < Result < string > > ( ( resolve ) => {
508+ this . pendingSyncPromises . push ( resolve ) ;
509+ } ) ;
510+ }
511+ this . isSyncing = true ;
512+
513+ const bestBlock = await promiseTimeout < THeader > (
514+ timeout ,
515+ this . getBestBlock ( ) ,
516+ ) ;
517+ if ( ! bestBlock ?. height ) {
518+ return this . retrySyncOrReturnError ( {
519+ timeout,
520+ retryAttempts,
521+ e : err ( 'Unable to get best block in syncLdk method.' ) ,
522+ } ) ;
523+ }
524+ const height = bestBlock . height ;
525+
526+ // Don't update unnecessarily
487527 if ( this . currentBlock . hash !== bestBlock ?. hash ) {
488- const syncToTip = await ldk . syncToTip ( bestBlock ) ;
528+ const syncToTip = await promiseTimeout < Result < string > > (
529+ timeout ,
530+ ldk . syncToTip ( bestBlock ) ,
531+ ) ;
489532 if ( syncToTip . isErr ( ) ) {
490- return syncToTip ;
533+ return this . retrySyncOrReturnError ( {
534+ timeout,
535+ retryAttempts,
536+ e : syncToTip ,
537+ } ) ;
491538 }
492539
493540 this . currentBlock = bestBlock ;
@@ -496,29 +543,147 @@ class LightningManager {
496543 let channels : TChannel [ ] = [ ] ;
497544 if ( this . watchTxs . length > 0 ) {
498545 // Get fresh array of channels.
499- const listChannelsResponse = await ldk . listChannels ( ) ;
546+ const listChannelsResponse = await promiseTimeout < Result < TChannel [ ] > > (
547+ timeout ,
548+ ldk . listChannels ( ) ,
549+ ) ;
500550 if ( listChannelsResponse . isOk ( ) ) {
501551 channels = listChannelsResponse . value ;
502552 }
503553 }
504554
505555 // Iterate over watch transactions/outputs and set whether they are confirmed or unconfirmed.
506- await this . checkWatchTxs ( this . watchTxs , channels , bestBlock ) ;
507- await this . checkWatchOutputs ( this . watchOutputs ) ;
508- await this . checkUnconfirmedTransactions ( ) ;
556+ const watchTxsRes = await promiseTimeout < Result < string > > (
557+ timeout ,
558+ this . checkWatchTxs ( this . watchTxs , channels , bestBlock ) ,
559+ ) ;
560+ if ( watchTxsRes . isErr ( ) ) {
561+ return this . retrySyncOrReturnError ( {
562+ timeout,
563+ retryAttempts,
564+ e : watchTxsRes ,
565+ } ) ;
566+ }
567+ const watchOutputsRes = await promiseTimeout < Result < string > > (
568+ timeout ,
569+ this . checkWatchOutputs ( this . watchOutputs ) ,
570+ ) ;
571+ if ( watchOutputsRes . isErr ( ) ) {
572+ return this . retrySyncOrReturnError ( {
573+ timeout,
574+ retryAttempts,
575+ e : watchOutputsRes ,
576+ } ) ;
577+ }
578+ const unconfirmedTxsRes = await promiseTimeout < Result < string > > (
579+ timeout ,
580+ this . checkUnconfirmedTransactions ( ) ,
581+ ) ;
582+ if ( unconfirmedTxsRes . isErr ( ) ) {
583+ return this . retrySyncOrReturnError ( {
584+ timeout,
585+ retryAttempts,
586+ e : unconfirmedTxsRes ,
587+ } ) ;
588+ }
589+
590+ this . isSyncing = false ;
509591
510- return ok ( `Synced to block ${ height } ` ) ;
592+ // Handle force sync if needed.
593+ if ( this . forceSync ) {
594+ return this . handleForceSync ( { timeout, retryAttempts } ) ;
595+ }
596+ const result = ok ( `Synced to block ${ height } ` ) ;
597+ this . resolveAllPendingSyncPromises ( result ) ;
598+ return result ;
511599 }
512600
601+ /**
602+ * Resolves all pending sync promises with the provided result.
603+ * @private
604+ * @param {Result<string> } result
605+ * @returns {void }
606+ */
607+ private resolveAllPendingSyncPromises ( result : Result < string > ) : void {
608+ while ( this . pendingSyncPromises . length > 0 ) {
609+ const resolve = this . pendingSyncPromises . shift ( ) ;
610+ if ( resolve ) {
611+ resolve ( result ) ;
612+ }
613+ }
614+ }
615+
616+ /**
617+ * Sets forceSync to false and re-runs the sync method.
618+ * @private
619+ * @param {number } timeout
620+ * @param {number } retryAttempts
621+ * @returns {Promise<Result<string>> }
622+ */
623+ private handleForceSync = async ( {
624+ timeout,
625+ retryAttempts,
626+ } : {
627+ timeout : number ;
628+ retryAttempts : number ;
629+ } ) : Promise < Result < string > > => {
630+ this . forceSync = false ;
631+ return this . syncLdk ( {
632+ timeout,
633+ retryAttempts,
634+ } ) ;
635+ } ;
636+
637+ /**
638+ * Attempts to retry the syncLdk method. Otherwise, the error gets passed to handleSyncError.
639+ * @private
640+ * @param {number } [timeout]
641+ * @param {number } retryAttempts
642+ * @param {Err<string> } e
643+ * @returns {Promise<Result<string>> }
644+ */
645+ private retrySyncOrReturnError = async ( {
646+ timeout = 5000 ,
647+ retryAttempts,
648+ e,
649+ } : {
650+ timeout ?: number ;
651+ retryAttempts : number ;
652+ e : Err < string > ;
653+ } ) : Promise < Result < string > > => {
654+ this . isSyncing = false ;
655+ if ( retryAttempts > 0 ) {
656+ await sleep ( ) ;
657+ return this . syncLdk ( {
658+ timeout,
659+ retryAttempts : retryAttempts - 1 ,
660+ } ) ;
661+ } else {
662+ return this . handleSyncError ( e ) ;
663+ }
664+ } ;
665+
666+ /**
667+ * Sets isSyncing & forceSync to false and returns error.
668+ * @private
669+ * @param {Err<string> } e
670+ * @returns {Promise<Result<string>> }
671+ */
672+ private handleSyncError = ( e : Err < string > ) : Result < string > => {
673+ this . isSyncing = false ;
674+ this . forceSync = false ;
675+ this . resolveAllPendingSyncPromises ( e ) ;
676+ return e ;
677+ } ;
678+
513679 checkWatchTxs = async (
514680 watchTxs : TRegisterTxEvent [ ] ,
515681 channels : TChannel [ ] ,
516682 bestBlock : THeader ,
517- ) : Promise < void > => {
683+ ) : Promise < Result < string > > => {
518684 const height = bestBlock ?. height ;
519685 if ( ! height ) {
520- console . log ( 'No height provided' ) ;
521- return ;
686+ return err ( 'No height provided' ) ;
522687 }
523688 await Promise . all (
524689 watchTxs . map ( async ( watchTxData ) => {
@@ -533,7 +698,7 @@ class LightningManager {
533698 //Watch TX was never confirmed so there's no need to unconfirm it.
534699 return ;
535700 }
536- if ( ! txData . transaction ) {
701+ if ( ! txData ? .transaction ) {
537702 console . log (
538703 'Unable to retrieve transaction data from the getTransactionData method.' ,
539704 ) ;
@@ -564,11 +729,12 @@ class LightningManager {
564729 }
565730 } ) ,
566731 ) ;
732+ return ok ( 'Watch transactions checked' ) ;
567733 } ;
568734
569735 checkWatchOutputs = async (
570736 watchOutputs : TRegisterOutputEvent [ ] ,
571- ) : Promise < void > => {
737+ ) : Promise < Result < string > > => {
572738 await Promise . all (
573739 watchOutputs . map ( async ( { index, script_pubkey } ) => {
574740 const transactions = await this . getScriptPubKeyHistory ( script_pubkey ) ;
@@ -630,6 +796,7 @@ class LightningManager {
630796 ) ;
631797 } ) ,
632798 ) ;
799+ return ok ( 'Watch outputs checked' ) ;
633800 } ;
634801
635802 /**
@@ -1127,7 +1294,7 @@ class LightningManager {
11271294 }
11281295 } ;
11291296
1130- checkUnconfirmedTransactions = async ( ) : Promise < void > => {
1297+ checkUnconfirmedTransactions = async ( ) : Promise < Result < string > > => {
11311298 let needsToSync = false ;
11321299 let newUnconfirmedTxs : TLdkUnconfirmedTransactions = [ ] ;
11331300 await Promise . all (
@@ -1169,8 +1336,9 @@ class LightningManager {
11691336
11701337 await this . updateUnconfirmedTxs ( newUnconfirmedTxs ) ;
11711338 if ( needsToSync ) {
1172- await this . syncLdk ( ) ;
1339+ await this . syncLdk ( { force : true } ) ;
11731340 }
1341+ return ok ( 'Unconfirmed transactions checked' ) ;
11741342 } ;
11751343
11761344 /**
@@ -1821,7 +1989,7 @@ class LightningManager {
18211989 ) : Promise < void > {
18221990 // Payment Received/Invoice Paid.
18231991 console . log ( `onChannelManagerPaymentClaimed: ${ JSON . stringify ( res ) } ` ) ;
1824- this . syncLdk ( ) . then ( ) ;
1992+ this . syncLdk ( { force : true } ) . then ( ) ;
18251993 }
18261994
18271995 /**
0 commit comments