Repository: skalenetwork/IMA (language: python)

Issue:
ticket-1413 SNB thread tries force download connected S-Chains until success when invoked first time at startu
- SNB thread tries force download connected S-Chains until success when invoked first time at startup
- added verified and tested support for both node js 18 and 20, where 18 is current LTS version and 20 will become main LTS version in January 2024
- switched node js version to 18
- fixed "to-boolean" type conversion everywhere it was possible, i.e. converted `something ? true : false` into `!!something` as D1 and D3 suggested


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/comprehensive.yml b/.github/workflows/comprehensive.yml
index d35d46307..50310b3ac 100644
--- a/.github/workflows/comprehensive.yml
+++ b/.github/workflows/comprehensive.yml
@@ -132,7 +132,7 @@ jobs:
 
     - name: INIT - install Node JS
       run: |
-        curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
+        curl -sL https://deb.nodesource.com/setup_18.x | sudo bash -
         sudo apt-get install -y nodejs
         sudo ln -s /usr/bin/node /usr/local/bin/node
         sudo npm install --global npm
diff --git a/Dockerfile b/Dockerfile
index 1c7765476..ad835bc34 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@ RUN apt-get update
 RUN apt-get install --no-install-recommends -y libssl1.1
 # NOTICE: to remove extra dep above: sudo rm /etc/apt/sources.list.d/focal-security.list
 
-RUN curl -sL https://deb.nodesource.com/setup_16.x | bash
+RUN curl -sL https://deb.nodesource.com/setup_18.x | bash
 RUN apt-get install --no-install-recommends -y nodejs
 RUN npm install npm --global
 RUN npm install --global yarn
diff --git a/agent/cli.mjs b/agent/cli.mjs
index bec7056fb..af7caa420 100644
--- a/agent/cli.mjs
+++ b/agent/cli.mjs
@@ -2586,7 +2586,7 @@ async function asyncCheckUrlAtStartup( u, name ) {
 
 function commonInitPrintSysInfo() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     if( isPrintGathered ) {
         log.write( cc.debug( "This process " ) + cc.sunny( "PID" ) +
             cc.debug( " is " ) + cc.bright( process.pid ) + "\n" );
@@ -2819,7 +2819,7 @@ function commonInitCheckContractPresences() {
 
 function commonInitPrintFoundContracts() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     // deposit_box_eth_address                    --> deposit_box_eth_abi
     // deposit_box_erc20_address                  --> deposit_box_erc20_abi
     // deposit_box_erc721_address                 --> deposit_box_erc721_abi
@@ -2922,7 +2922,7 @@ function commonInitPrintFoundContracts() {
 
 function commonInitCheckErc20() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     let n1 = 0;
     let n2 = 0;
     if( imaState.chainProperties.mn.strPathJsonErc20.length > 0 ) {
@@ -3122,7 +3122,7 @@ function commonInitCheckErc20() {
 
 function commonInitCheckErc721() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     let n1 = 0;
     let n2 = 0;
     if( imaState.chainProperties.mn.strPathJsonErc721.length > 0 ) {
@@ -3319,7 +3319,7 @@ function commonInitCheckErc721() {
 
 function commonInitCheckErc1155() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     let n1 = 0;
     let n2 = 0;
     if( imaState.chainProperties.mn.strPathJsonErc1155.length > 0 ) {
@@ -3520,8 +3520,8 @@ function commonInitCheckErc1155() {
 
 function commonInitCheckGeneralArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
-    const isPrintSecurityValues = imaState.isPrintSecurityValues ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
+    const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) );
     if( isPrintGathered ) {
         printAbout( true );
         log.write(
@@ -3643,8 +3643,8 @@ function commonInitCheckGeneralArgs() {
 
 function commonInitCheckCredentialsArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
-    const isPrintSecurityValues = imaState.isPrintSecurityValues ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
+    const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) );
     try {
         ensureHaveCredentials(
             "Main Net",
@@ -3694,7 +3694,7 @@ function commonInitCheckCredentialsArgs() {
 
 function commonInitCheckTransferAmountArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     ensureHaveValue(
         "Amount of wei to transfer", imaState.nAmountOfWei,
         false, isPrintGathered, null, ( x ) => {
@@ -3704,7 +3704,7 @@ function commonInitCheckTransferAmountArgs() {
 
 function commonInitTransferringArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     ensureHaveValue(
         "M->S transfer block size", imaState.nTransferBlockSizeM2S,
         false, isPrintGathered, null, ( x ) => {
@@ -3812,7 +3812,7 @@ function commonInitTransferringArgs() {
 
 function commonInitCheckAccessArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     ensureHaveValue(
         "S-Chain node number(zero based)",
         imaState.nNodeNumber, false, isPrintGathered, null, ( x ) => {
@@ -3827,7 +3827,7 @@ function commonInitCheckAccessArgs() {
 
 function commonInitErcTokensArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     if( imaState.chainProperties.tc.strCoinNameErc20.length > 0 ) {
         ensureHaveValue(
             "Loaded Main-net ERC20 ABI ",
@@ -3956,7 +3956,7 @@ function commonInitErcTokensArgs() {
 
 function commonInitGasMultipliersAndTransactionArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     if( isPrintGathered ) {
         log.write(
             cc.info( "Main Net Gas Price Multiplier is" ) +
@@ -4082,7 +4082,7 @@ function commonInitGasMultipliersAndTransactionArgs() {
 
 function commonInitLoggingArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
     if( imaState.strLogFilePath.length > 0 ) {
         ensureHaveValue(
             "Log file path",
@@ -4107,8 +4107,8 @@ function commonInitLoggingArgs() {
 
 function commonInitAutomaticExitArgs() {
     const imaState = state.get();
-    const isPrintGathered = imaState.isPrintGathered ? true : false;
-    const isPrintSecurityValues = imaState.isPrintSecurityValues ? true : false;
+    const isPrintGathered = ( !!( imaState.isPrintGathered ) );
+    const isPrintSecurityValues = ( !!( imaState.isPrintSecurityValues ) );
     ensureHaveValue(
         "Automatic exit(seconds)",
         imaState.nAutoExitAfterSeconds, false,
diff --git a/agent/discoveryTools.mjs b/agent/discoveryTools.mjs
index dee239e98..8e90f21b3 100644
--- a/agent/discoveryTools.mjs
+++ b/agent/discoveryTools.mjs
@@ -61,7 +61,8 @@ export function initialSkaleNetworkScanForS2S() {
                 "secondsToWaitForSkaleNetworkDiscovered":
                     imaState.optsS2S.secondsToWaitForSkaleNetworkDiscovered,
                 "chain": imaState.chainProperties.sc,
-                "bParallelModeRefreshSNB": imaState.optsS2S.bParallelModeRefreshSNB ? true : false
+                "bParallelModeRefreshSNB": ( !!( imaState.optsS2S.bParallelModeRefreshSNB ) ),
+                "isForceMultiAttemptsUntilSuccess": true
             };
             if( log.verboseGet() >= log.verboseReversed().information ) {
                 log.write( strLogPrefix +
diff --git a/agent/loopWorker.mjs b/agent/loopWorker.mjs
index b0c66e2ce..1afd15410 100644
--- a/agent/loopWorker.mjs
+++ b/agent/loopWorker.mjs
@@ -198,7 +198,7 @@ class ObserverServer extends SocketServer {
                     owaspUtils.toInteger( joMessage.params.nNodeNumber ),
                     joMessage.params.strLoopWorkType,
                     joMessage.params.nIndexS2S,
-                    joMessage.params.isStart ? true : false,
+                    ( !!( joMessage.params.isStart ) ),
                     owaspUtils.toInteger( joMessage.params.ts ),
                     joMessage.params.signature
                 );
diff --git a/agent/main.mjs b/agent/main.mjs
index 9e7126485..e8b345189 100644
--- a/agent/main.mjs
+++ b/agent/main.mjs
@@ -359,7 +359,7 @@ function initJsonRpcServer() {
                     owaspUtils.toInteger( joMessage.params.nNodeNumber ),
                     joMessage.params.strLoopWorkType,
                     joMessage.params.nIndexS2S,
-                    joMessage.params.isStart ? true : false,
+                    ( !!( joMessage.params.isStart ) ),
                     owaspUtils.toInteger( joMessage.params.ts ),
                     joMessage.params.signature
                 ) )
@@ -384,7 +384,8 @@ function initJsonRpcServer() {
                             imaState.optsS2S.secondsToWaitForSkaleNetworkDiscovered,
                         "chain": imaState.chainProperties.sc,
                         "bParallelModeRefreshSNB":
-                            imaState.optsS2S.bParallelModeRefreshSNB ? true : false
+                            ( !!( imaState.optsS2S.bParallelModeRefreshSNB ) ),
+                        "isForceMultiAttemptsUntilSuccess": false
                     };
                     skaleObserver.refreshNowSNB( opts ); // async call, no await here
                     joAnswer.result = "Done";
diff --git a/agent/pwa.mjs b/agent/pwa.mjs
index 0d04225e6..81372f6ed 100644
--- a/agent/pwa.mjs
+++ b/agent/pwa.mjs
@@ -230,7 +230,7 @@ export async function handleLoopStateArrived(
                 strMessageHash, nNodeNumber, signature, imaState.isPrintPWA );
         if( ! isSignatureOK )
             throw new Error( "BLS verification failed" );
-        joProps.isInProgress = isStart ? true : false;
+        joProps.isInProgress = ( !!isStart );
         joProps.ts = 0 + ts;
         if( log.verboseGet() >= log.verboseReversed().error ) {
             if( imaState.isPrintPWA ) {
@@ -310,7 +310,7 @@ async function notifyOnLoopImpl( imaState, strLoopWorkType, nIndexS2S, isStart )
                             "nNodeNumber": 0 + imaState.nNodeNumber,
                             "strLoopWorkType": "" + strLoopWorkType,
                             "nIndexS2S": 0 + nIndexS2S,
-                            "isStart": isStart ? true : false,
+                            "isStart": ( !!isStart ),
                             "ts": nUtcUnixTimeStamp,
                             "signature": signature
                         }
diff --git a/agent/threadInfo.mjs b/agent/threadInfo.mjs
index 6a94cbc02..1e9c6767d 100644
--- a/agent/threadInfo.mjs
+++ b/agent/threadInfo.mjs
@@ -38,7 +38,7 @@ export function getCurrentThreadID() {
 }
 
 export function isMainThread() {
-    return worker_threads.isMainThread ? true : false;
+    return ( !!( worker_threads.isMainThread ) );
 }
 
 export function threadDescription( isColorized ) {
diff --git a/npms/skale-cool-socket/socket.mjs b/npms/skale-cool-socket/socket.mjs
index 3b9ddf754..2309ce498 100644
--- a/npms/skale-cool-socket/socket.mjs
+++ b/npms/skale-cool-socket/socket.mjs
@@ -245,7 +245,7 @@ export class BasicSocketPipe extends EventDispatcher {
             this.implSend( data );
             return;
         }
-        isFlush = ( isFlush == undefined || isFlush == null ) ? true : ( isFlush ? true : false );
+        isFlush = ( isFlush == undefined || isFlush == null ) ? true : ( !!( isFlush ) );
         const jo = socketReceivedDataReverseMarshall( data );
         if( settings.logging.net.socket.accumulate )
             console.log( this.socketLoggingTextPrefix( "accumulate" ), data );
@@ -664,10 +664,10 @@ export class OutOfWorkerRelay extends EventDispatcher {
             ? ( "" + strRelayName ) : "unnamed";
         self.isAutoFlushIncoming =
             ( isAutoFlushIncoming == null || isAutoFlushIncoming == undefined )
-                ? true : ( isAutoFlushIncoming ? true : false );
+                ? true : ( !!isAutoFlushIncoming );
         self.isAutoFlushOutgoing =
             ( isAutoFlushOutgoing == null || isAutoFlushOutgoing == undefined )
-                ? true : ( isAutoFlushOutgoing ? true : false );
+                ? true : ( !!( isAutoFlushOutgoing ) );
         if( ! acceptor ) {
             throw new Error( "OutOfWorkerRelay \"" + self.strRelayName +
                 "\" needs acceptor for normal functionality" );
@@ -898,10 +898,10 @@ export class OneToOneRelay extends EventDispatcher {
                 ? ( "" + strRelayName ) : "unnamed";
         self.isAutoFlushIncoming =
             ( isAutoFlushIncoming == null || isAutoFlushIncoming == undefined )
-                ? true : ( isAutoFlushIncoming ? true : false );
+                ? true : ( !!isAutoFlushIncoming );
         self.isAutoFlushOutgoing =
             ( isAutoFlushOutgoing == null || isAutoFlushOutgoing == undefined )
-                ? true : ( isAutoFlushOutgoing ? true : false );
+                ? true : ( !!isAutoFlushIncoming );
         self.pipeIncoming = pipeIncoming;
         self.pipeOutgoing = pipeOutgoing;
         if( ( !( "strSavedRemoteAddress" in pipeIncoming ) ) ||
@@ -1129,7 +1129,7 @@ export class OneToOneRelay extends EventDispatcher {
 export class DirectPipe extends BasicSocketPipe {
     constructor( counterPipe, isBroadcastOpenEvents ) {
         super();
-        isBroadcastOpenEvents = isBroadcastOpenEvents ? true : false;
+        isBroadcastOpenEvents = ( !!isBroadcastOpenEvents );
         this.socketType = "Direct";
         this.socketSubtype = "direct.not.initialized.yet";
         this.isConnected = false;
@@ -3289,7 +3289,7 @@ export class WebRTCClientPipe extends BasicSocketPipe {
         this.isConnected = false;
         this.rtcPeer = null;
         this.isAutoCloseSignalingPipeOnDataChannelOpen =
-            settings.net.rtc.isAutoCloseSignalingPipeOnDataChannelOpen ? true : false;
+            ( !!( settings.net.rtc.isAutoCloseSignalingPipeOnDataChannelOpen ) );
         this.url = "rtc_client_pipe-" + this.strSignalingServerURL;
         this.reconnect();
     }
diff --git a/npms/skale-cool-socket/socketUtils.mjs b/npms/skale-cool-socket/socketUtils.mjs
index 9173bcdb4..b9a19d636 100644
--- a/npms/skale-cool-socket/socketUtils.mjs
+++ b/npms/skale-cool-socket/socketUtils.mjs
@@ -59,13 +59,13 @@ export const randomString = function(
     if( length <= 0 )
         return "";
     isABC = ( isABC == null || isABC == undefined )
-        ? true : ( isABC ? true : false );
+        ? true : ( !!isABC );
     isDigits = ( isDigits == null || isDigits == undefined )
-        ? false : ( isDigits ? true : false );
+        ? false : ( !!isDigits );
     isSpecChr = ( isSpecChr == null || isSpecChr == undefined )
-        ? false : ( isSpecChr ? true : false );
+        ? false : ( !!isSpecChr );
     isPunctuation = ( isPunctuation == null || isPunctuation == undefined )
-        ? false : ( isPunctuation ? true : false );
+        ? false : ( !!isPunctuation );
     let arrCharacters = "";
     if( isABC )
         arrCharacters += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -213,8 +213,8 @@ export const formatDateTime = function(
         return "undefined-date-time";
     if( ! ( dt instanceof Date ) )
         return "not-a-date-time";
-    isDate = ( isDate == null || isDate == undefined ) ? true : ( isDate ? true : false );
-    isTime = ( isTime == null || isTime == undefined ) ? true : ( isTime ? true : false );
+    isDate = ( isDate == null || isDate == undefined ) ? true : ( !!isDate );
+    isTime = ( isTime == null || isTime == undefined ) ? true : ( !!isTime );
     if( ( !isDate ) && ( !isTime ) )
         return "";
     let s = "";
@@ -247,7 +247,7 @@ export const formatDateTime = function(
             sepDate +
             zeroPaddingLeft( dt.getSeconds(), 2 );
         isMilliseconds = ( isMilliseconds == null || isMilliseconds == undefined )
-            ? true : ( isMilliseconds ? true : false );
+            ? true : ( !!isMilliseconds );
         if( isMilliseconds ) {
             sepMilliseconds =
                 ( sepMilliseconds == null ||
diff --git a/npms/skale-ima/imaHelperAPIs.mjs b/npms/skale-ima/imaHelperAPIs.mjs
index f2facc866..8046156ef 100644
--- a/npms/skale-ima/imaHelperAPIs.mjs
+++ b/npms/skale-ima/imaHelperAPIs.mjs
@@ -52,10 +52,10 @@ export function setSleepBetweenTransactionsOnSChainMilliseconds( val ) {
 }
 
 export function getWaitForNextBlockOnSChain() {
-    return gFlagWaitForNextBlockOnSChain ? true : false;
+    return ( !!gFlagWaitForNextBlockOnSChain );
 }
 export function setWaitForNextBlockOnSChain( val ) {
-    gFlagWaitForNextBlockOnSChain = val ? true : false;
+    gFlagWaitForNextBlockOnSChain = ( !!val );
 }
 
 export const sleep = ( milliseconds ) => {
@@ -202,17 +202,17 @@ export function getS2STransferModeDescriptionColorized() {
 }
 
 export function isForwardS2S() {
-    return gFlagIsForwardS2S ? true : false;
+    return ( !!gFlagIsForwardS2S );
 }
 
 export function isReverseS2S() {
-    return gFlagIsForwardS2S ? false : true;
+    return ( !!gFlagIsForwardS2S );
 }
 
 export function setForwardS2S( b ) {
     if( b == null || b == undefined )
         b = true;
-    gFlagIsForwardS2S = b ? true : false;
+    gFlagIsForwardS2S = ( !!b );
 }
 
 export function setReverseS2S( b ) {
diff --git a/npms/skale-ima/imaOracleOperations.mjs b/npms/skale-ima/imaOracleOperations.mjs
index 1e774863f..5d25b8a32 100644
--- a/npms/skale-ima/imaOracleOperations.mjs
+++ b/npms/skale-ima/imaOracleOperations.mjs
@@ -35,10 +35,10 @@ import * as imaTransferErrorHandling from "./imaTransferErrorHandling.mjs";
 let gFlagIsEnabledOracle = false;
 
 export function getEnabledOracle( isEnabled ) {
-    return gFlagIsEnabledOracle ? true : false;
+    return ( !!gFlagIsEnabledOracle );
 }
 export function setEnabledOracle( isEnabled ) {
-    gFlagIsEnabledOracle = isEnabled ? true : false;
+    gFlagIsEnabledOracle = ( !!isEnabled );
 }
 
 async function prepareOracleGasPriceSetup( optsGasPriseSetup ) {
diff --git a/npms/skale-ima/imaTransferErrorHandling.mjs b/npms/skale-ima/imaTransferErrorHandling.mjs
index f31868c67..b4c73c67d 100644
--- a/npms/skale-ima/imaTransferErrorHandling.mjs
+++ b/npms/skale-ima/imaTransferErrorHandling.mjs
@@ -89,8 +89,8 @@ export function getLastErrorCategories() {
 let gFlagIsEnabledProgressiveEventsScan = true;
 
 export function getEnabledProgressiveEventsScan() {
-    return gFlagIsEnabledProgressiveEventsScan ? true : false;
+    return ( !!gFlagIsEnabledProgressiveEventsScan );
 }
 export function setEnabledProgressiveEventsScan( isEnabled ) {
-    gFlagIsEnabledProgressiveEventsScan = isEnabled ? true : false;
+    gFlagIsEnabledProgressiveEventsScan = ( !!isEnabled );
 }
diff --git a/npms/skale-ima/imaTx.mjs b/npms/skale-ima/imaTx.mjs
index ff6d0b342..d998d0af2 100644
--- a/npms/skale-ima/imaTx.mjs
+++ b/npms/skale-ima/imaTx.mjs
@@ -45,24 +45,24 @@ let redis = null;
 let gFlagDryRunIsEnabled = true;
 
 export function dryRunIsEnabled() {
-    return gFlagDryRunIsEnabled ? true : false;
+    return ( !!gFlagDryRunIsEnabled );
 }
 export function dryRunEnable( isEnable ) {
     gFlagDryRunIsEnabled = ( isEnable != null && isEnable != undefined )
-        ? ( isEnable ? true : false ) : true;
-    return gFlagDryRunIsEnabled ? true : false;
+        ? ( !!isEnable ) : true;
+    return ( !!gFlagDryRunIsEnabled );
 }
 
 let gFlagDryRunIsIgnored = true;
 
 export function dryRunIsIgnored() {
-    return gFlagDryRunIsIgnored ? true : false;
+    return ( !!gFlagDryRunIsIgnored );
 }
 
 export function dryRunIgnore( isIgnored ) {
     gFlagDryRunIsIgnored = ( isIgnored != null && isIgnored != undefined )
-        ? ( isIgnored ? true : false ) : true;
-    return gFlagDryRunIsIgnored ? true : false;
+        ? ( !!isIgnored ) : true;
+    return ( !!gFlagDryRunIsIgnored );
 }
 
 export async function dryRunCall(
@@ -76,7 +76,7 @@ export async function dryRunCall(
     if( ! dryRunIsEnabled() )
         return null; // success
     isDryRunResultIgnore = ( isDryRunResultIgnore != null && isDryRunResultIgnore != undefined )
-        ? ( isDryRunResultIgnore ? true : false ) : false;
+        ? ( !!isDryRunResultIgnore ) : false;
     const strContractMethodDescription =
         cc.notice( strContractName ) + cc.debug( "(" ) + cc.info( joContract.address ) +
         cc.debug( ")." ) + cc.notice( strMethodName );
diff --git a/npms/skale-log/log.mjs b/npms/skale-log/log.mjs
index c7a460c0b..032d4d030 100644
--- a/npms/skale-log/log.mjs
+++ b/npms/skale-log/log.mjs
@@ -37,7 +37,7 @@ export function getPrintTimestamps() {
 }
 
 export function setPrintTimestamps( b ) {
-    gFlagLogWithTimeStamps = b ? true : false;
+    gFlagLogWithTimeStamps = ( !!b );
 }
 
 export function n2s( n, sz ) {
@@ -50,7 +50,7 @@ export function n2s( n, sz ) {
 export function generateTimestampString( ts, isColorized ) {
     isColorized =
         ( typeof isColorized == "undefined" )
-            ? true : ( isColorized ? true : false );
+            ? true : ( !!isColorized );
     ts = ( ts instanceof Date ) ? ts : new Date();
     const ccDate = function( x ) { return isColorized ? cc.date( x ) : x; };
     const ccTime = function( x ) { return isColorized ? cc.time( x ) : x; };
@@ -422,10 +422,10 @@ let gFlagIsExposeDetails = false;
 let gVerboseLevel = 0 + verboseReversed().information;
 
 export function exposeDetailsGet() {
-    return gFlagIsExposeDetails ? true : false;
+    return ( !!gFlagIsExposeDetails );
 }
 export function exposeDetailsSet( isExpose ) {
-    gFlagIsExposeDetails = isExpose ? true : false;
+    gFlagIsExposeDetails = ( !!isExpose );
 }
 
 export function verboseGet() {
diff --git a/npms/skale-observer/observer.mjs b/npms/skale-observer/observer.mjs
index 41a10f63b..e6e4a75cf 100644
--- a/npms/skale-observer/observer.mjs
+++ b/npms/skale-observer/observer.mjs
@@ -1261,7 +1261,11 @@ async function parallelPeriodicCachingStart( strChainNameConnectedTo, opts ) {
                     parseInt( opts.secondsToReDiscoverSkaleNetwork ),
                 "secondsToWaitForSkaleNetworkDiscovered":
                     parseInt( opts.secondsToWaitForSkaleNetworkDiscovered ),
-                "strChainNameConnectedTo": strChainNameConnectedTo
+                "strChainNameConnectedTo": strChainNameConnectedTo,
+                "isForceMultiAttemptsUntilSuccess":
+                    ( "isForceMultiAttemptsUntilSuccess" in opts &&
+                    opts.isForceMultiAttemptsUntilSuccess )
+                        ? true : false
             }
         };
         gClient.send( jo );
diff --git a/npms/skale-observer/observerWorker.mjs b/npms/skale-observer/observerWorker.mjs
index 005884ef0..4a2f911aa 100644
--- a/npms/skale-observer/observerWorker.mjs
+++ b/npms/skale-observer/observerWorker.mjs
@@ -167,7 +167,8 @@ class ObserverServer extends SocketServer {
                 self.periodicCachingStart(
                     socket,
                     joMessage.message.secondsToReDiscoverSkaleNetwork,
-                    joMessage.message.strChainNameConnectedTo
+                    joMessage.message.strChainNameConnectedTo,
+                    joMessage.message.isForceMultiAttemptsUntilSuccess
                 );
                 joAnswer.message = {
                     "method": "" + joMessage.method,
@@ -200,19 +201,34 @@ class ObserverServer extends SocketServer {
         super.dispose();
     }
     async periodicCachingDoNow(
-        socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo
+        socket,
+        secondsToReDiscoverSkaleNetwork,
+        strChainNameConnectedTo,
+        isForceMultiAttemptsUntilSuccess
     ) {
         const self = this;
         if( self.bIsPeriodicCachingStepInProgress )
             return null;
         let strError = null;
         self.bIsPeriodicCachingStepInProgress = true;
-        for( let idxAttempt = 0; idxAttempt < 10; ++ idxAttempt ) {
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            self.opts.details.write( threadInfo.threadDescription() +
+                cc.debug( " thread will invoke S-Chains caching in " ) +
+                ( isForceMultiAttemptsUntilSuccess
+                    ? cc.warning( "forced" )
+                    : cc.success( "normal" ) ) +
+                cc.debug( " mode..." ) + "\n" );
+        }
+        for( let idxAttempt = 0;
+            // eslint-disable-next-line no-unmodified-loop-condition
+            idxAttempt < 10 || isForceMultiAttemptsUntilSuccess;
+            ++ idxAttempt
+        ) {
             try {
                 if( log.verboseGet() >= log.verboseReversed().debug ) {
                     self.opts.details.write( threadInfo.threadDescription() +
-                        cc.debug( " thread will invoke S-Chains caching..." ) +
-                        "\n" );
+                        cc.debug( " thread will invoke S-Chains caching(attempt + " ) +
+                        cc.info( idxAttempt ) + cc.debug( ")..." ) + "\n" );
                 }
                 strError =
                     await skaleObserver.cacheSChains(
@@ -259,7 +275,12 @@ class ObserverServer extends SocketServer {
         }
         return null;
     }
-    async periodicCachingStart( socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo ) {
+    async periodicCachingStart(
+        socket,
+        secondsToReDiscoverSkaleNetwork,
+        strChainNameConnectedTo,
+        isForceMultiAttemptsUntilSuccess
+    ) {
         const self = this;
         await self.periodicCachingStop();
         if( secondsToReDiscoverSkaleNetwork <= 0 )
@@ -272,8 +293,19 @@ class ObserverServer extends SocketServer {
                         cc.debug( " will do immediate periodic SNB refresh..." ) +
                         "\n" );
                 }
-                await self.periodicCachingDoNow(
-                    socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo );
+                while( true ) {
+                    const strError =
+                        await self.periodicCachingDoNow(
+                            socket,
+                            secondsToReDiscoverSkaleNetwork,
+                            strChainNameConnectedTo,
+                            ( !!isForceMultiAttemptsUntilSuccess )
+                        );
+                    if( strError && isForceMultiAttemptsUntilSuccess )
+                        continue;
+                    isForceMultiAttemptsUntilSuccess = false;
+                    break;
+                }
             } catch ( err ) {
                 if( log.verboseGet() >= log.verboseReversed().error ) {
                     self.log( cc.error( "Periodic SNB caching(async) error in " ) +
diff --git a/npms/skale-owasp/owaspUtils.mjs b/npms/skale-owasp/owaspUtils.mjs
index 9e1f074ba..5a463fee3 100644
--- a/npms/skale-owasp/owaspUtils.mjs
+++ b/npms/skale-owasp/owaspUtils.mjs
@@ -646,7 +646,7 @@ function moneyUnitNameToPostMultiplier( s ) {
 
 export function parseMoneySpecToWei( s, isThrowException ) {
     try {
-        isThrowException = isThrowException ? true : false;
+        isThrowException = ( !!isThrowException );
         if( s == null || s == undefined ) {
             if( isThrowException )
                 throw new Error( "no parse-able value provided" );


Test patch:
diff --git a/.github/workflows/container-test.yml b/.github/workflows/container-test.yml
index 94fe0ffa9..f410d1f2d 100644
--- a/.github/workflows/container-test.yml
+++ b/.github/workflows/container-test.yml
@@ -132,7 +132,7 @@ jobs:
 
     - name: INIT - install Node JS
       run: |
-        curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
+        curl -sL https://deb.nodesource.com/setup_18.x | sudo bash -
         sudo apt-get install -y nodejs
         sudo ln -s /usr/bin/node /usr/local/bin/node
         sudo npm install --global npm
diff --git a/npms/skale-cool-socket/testSocketSignalingServer.mjs b/npms/skale-cool-socket/testSocketSignalingServer.mjs
index a13990014..b79075663 100644
--- a/npms/skale-cool-socket/testSocketSignalingServer.mjs
+++ b/npms/skale-cool-socket/testSocketSignalingServer.mjs
@@ -408,8 +408,7 @@ class SignalingCategory extends EventDispatcher {
         try {
             idSpace = "" + ( idSpace ? idSpace.toString() : settings.rtcSpace.defaultSpaceName );
             isAutoAlloc =
-                ( isAutoAlloc == null || isAutoAlloc == undefined ) ? true : ( isAutoAlloc
-                    ? true : false );
+                ( isAutoAlloc == null || isAutoAlloc == undefined ) ? true : ( !!isAutoAlloc );
             let signalingSpace = null;
             if( idSpace in this.mapSpaces )
                 signalingSpace = this.mapSpaces[idSpace];
@@ -477,7 +476,7 @@ class SignalingManager extends EventDispatcher {
             idCategory = "" + ( idCategory
                 ? idCategory.toString() : settings.rtcSpace.defaultSpaceCategory );
             isAutoAlloc = ( isAutoAlloc == null || isAutoAlloc == undefined )
-                ? true : ( isAutoAlloc ? true : false );
+                ? true : ( !!isAutoAlloc );
             let signalingCategory = null;
             if( idCategory in this.mapCategories )
                 signalingCategory = this.mapCategories[idCategory];