Repository: skalenetwork/IMA (language: python)

Issue:
Ticket 1554/not found outgoing message event from 1st attempt
Fixed issue related to not found OutgoingMessage event on first iteration of search


Discussion / hints:

Fix patch:
diff --git a/agent/bls.mjs b/agent/bls.mjs
index fa0f7059e..af7b2316c 100644
--- a/agent/bls.mjs
+++ b/agent/bls.mjs
@@ -1477,7 +1477,7 @@ async function doSignConfigureChainAccessParams( optsSignOperation ) {
 async function doSignProcessHandleCall(
     optsSignOperation,
     joNode, joParams,
-    joIn, joOut, err
+    joIn, joOut, err, strNodeURL, i
 ) {
     ++optsSignOperation.joGatheringTracker.nCountReceived; // including errors
     if( err ) {
@@ -1485,11 +1485,11 @@ async function doSignProcessHandleCall(
         const strErrorMessage =
             optsSignOperation.strLogPrefix + cc.fatal( "CRITICAL ERROR:" ) +
             cc.error( " JSON RPC call to S-Chain node " ) + strNodeDescColorized +
-            cc.error( " failed, RPC call reported error: " ) +
-            cc.warning( owaspUtils.extractErrorMessage( err ) ) +
-            cc.error( ", " ) + cc.notice( "sequence ID" ) +
-            cc.error( " is " ) + cc.attention( optsSignOperation.sequenceId ) +
-            "\n";
+            cc.error( "(node #" ) + cc.info( i ) + cc.error( " via " ) +
+            cc.notice( strNodeURL ) + cc.error( ") failed, RPC call reported error: " ) +
+            cc.warning( owaspUtils.extractErrorMessage( err ) ) + cc.error( ", " ) +
+            cc.notice( "sequence ID" ) + cc.error( " is " ) +
+            cc.attention( optsSignOperation.sequenceId ) + "\n";
         if( log.verboseGet() >= log.verboseReversed().error ) {
             if( log.id != optsSignOperation.details.id )
                 log.write( strErrorMessage );
@@ -1502,7 +1502,8 @@ async function doSignProcessHandleCall(
         optsSignOperation.details.write( optsSignOperation.strLogPrefix +
             log.generateTimestampString( null, true ) + " " +
             cc.debug( "Got answer from " ) + cc.info( "skale_imaVerifyAndSign" ) +
-            cc.debug( " for transfer from chain " ) + cc.info( optsSignOperation.fromChainName ) +
+            cc.error( "(node #" ) + cc.info( i ) + cc.error( " via " ) + cc.notice( strNodeURL ) +
+            cc.debug( ") for transfer from chain " ) + cc.info( optsSignOperation.fromChainName ) +
             cc.debug( " to chain " ) + cc.info( optsSignOperation.targetChainName ) +
             cc.debug( " with params " ) + cc.j( joParams ) + cc.debug( ", answer is " ) +
             cc.j( joOut ) + cc.debug( ", " ) + cc.notice( "sequence ID" ) + cc.debug( " is " ) +
@@ -1713,7 +1714,9 @@ async function doSignProcessOneImpl( i, optsSignOperation ) {
             if( log.verboseGet() >= log.verboseReversed().trace ) {
                 optsSignOperation.details.write( optsSignOperation.strLogPrefix +
                     log.generateTimestampString( null, true ) + " " + cc.debug( "Will invoke " ) +
-                    cc.info( "skale_imaVerifyAndSign" ) + cc.debug( " for transfer from chain " ) +
+                    cc.info( "skale_imaVerifyAndSign" ) + cc.debug( " to node #" ) + cc.info( i ) +
+                    cc.debug( " via " ) + cc.notice( strNodeURL ) +
+                    cc.debug( " for transfer from chain " ) +
                     cc.info( optsSignOperation.fromChainName ) + cc.debug( " to chain " ) +
                     cc.info( optsSignOperation.targetChainName ) + cc.debug( " with params " ) +
                     cc.j( joParams ) + cc.debug( ", " ) + cc.notice( "sequence ID" ) +
@@ -1724,9 +1727,7 @@ async function doSignProcessOneImpl( i, optsSignOperation ) {
                 "params": joParams
             }, async function( joIn, joOut, err ) {
                 await doSignProcessHandleCall(
-                    optsSignOperation,
-                    joNode, joParams,
-                    joIn, joOut, err
+                    optsSignOperation, joNode, joParams, joIn, joOut, err, strNodeURL, i
                 );
             } ); // joCall.call ...
         } ); // rpcCall.create ...
diff --git a/agent/cli.mjs b/agent/cli.mjs
index 6b8a927dc..bec7056fb 100644
--- a/agent/cli.mjs
+++ b/agent/cli.mjs
@@ -743,13 +743,13 @@ function printHelpTransfers( soi ) {
     console.log( soi + cc.debug( "--" ) +
         cc.bright( "s2s-parallel" ) + cc.debug( ".........................." ) +
         cc.notice( "Sets " ) + " " + cc.note( "parallel S2S transfer mode" ) +
-        cc.notice( " and runs S2S in worker thread." )
+        cc.notice( " and runs S2S in worker thread." ) +
+        " " + cc.debug( "This is default mode" ) + cc.notice( ". " )
     );
     console.log( soi + cc.debug( "--" ) +
         cc.bright( "s2s-simple" ) + cc.debug( "............................" ) +
         cc.notice( "Sets " ) + " " + cc.note( "simple S2S transfer mode" ) +
-        cc.notice( " and runs S2S in main thread." ) +
-        " " + cc.debug( "This is default mode" ) + cc.notice( ". " )
+        cc.notice( " and runs S2S in main thread." )
     );
     console.log( soi + cc.debug( "--" ) +
         cc.bright( "net-rediscover" ) + cc.sunny( "=" ) + cc.attention( "number" ) +
diff --git a/agent/clpTools.mjs b/agent/clpTools.mjs
index 3f2dd7bb3..e153c8e5a 100644
--- a/agent/clpTools.mjs
+++ b/agent/clpTools.mjs
@@ -1478,7 +1478,7 @@ export function commandLineTaskLoopSimple() {
     const imaState = state.get();
     discoveryTools.initialSkaleNetworkScanForS2S();
     imaState.arrActions.push( {
-        "name": "M<->S and S->S transfer loop, simple mode",
+        "name": "M<->S and S->S transfer loop, startup simple mode",
         "fn": async function() {
             state.setPreventExitAfterLastAction( true );
             if( ! imaState.bNoWaitSChainStarted )
@@ -1654,8 +1654,7 @@ export function commandLineTaskBrowseSkaleNetwork() {
                 "details": log,
                 "bStopNeeded": false
             };
-            const addressFrom = imaState.chainProperties.mn.joAccount.address();
-            const arrSChains = await skaleObserver.loadSChains( addressFrom, opts );
+            const arrSChains = await skaleObserver.loadSChains( opts );
             const cnt = arrSChains.length;
             log.write( strLogPrefix + cc.normal( "Got " ) + cc.info( cnt ) +
                 cc.normal( " S-Chains(s) in SKALE NETWORK information: " ) +
@@ -1684,16 +1683,12 @@ export function commandLineTaskBrowseConnectedSChains() {
                     cc.debug( "Downloading SKALE network information..." ) + "\n" );
             }
             const opts = {
-                imaState: imaState,
+                "imaState": imaState,
                 "details": log,
                 "bStopNeeded": false
             };
-            const addressFrom = imaState.chainProperties.mn.joAccount.address();
             const arrSChainsCached = await skaleObserver.loadSChainsConnectedOnly(
-                imaState.chainProperties.sc.strChainName,
-                addressFrom,
-                opts
-            );
+                imaState.chainProperties.sc.strChainName, opts );
             const cnt = arrSChainsCached.length;
             log.write( strLogPrefix + cc.normal( "Got " ) + cc.info( cnt ) +
                 cc.normal( " connected S-Chain(s): " ) + cc.j( arrSChainsCached ) + "\n" );
diff --git a/agent/discoveryTools.mjs b/agent/discoveryTools.mjs
index 139f2c253..dee239e98 100644
--- a/agent/discoveryTools.mjs
+++ b/agent/discoveryTools.mjs
@@ -63,14 +63,12 @@ export function initialSkaleNetworkScanForS2S() {
                 "chain": imaState.chainProperties.sc,
                 "bParallelModeRefreshSNB": imaState.optsS2S.bParallelModeRefreshSNB ? true : false
             };
-            const addressFrom = imaState.chainProperties.mn.joAccount.address();
             if( log.verboseGet() >= log.verboseReversed().information ) {
                 log.write( strLogPrefix +
                     cc.debug( "Will start periodic S-Chains caching..." ) + "\n" );
             }
             await skaleObserver.periodicCachingStart(
                 imaState.chainProperties.sc.strChainName,
-                addressFrom,
                 opts
             );
             if( log.verboseGet() >= log.verboseReversed().information ) {
diff --git a/agent/loop.mjs b/agent/loop.mjs
index f35898c34..957d28337 100644
--- a/agent/loop.mjs
+++ b/agent/loop.mjs
@@ -1,4 +1,3 @@
-
 // SPDX-License-Identifier: AGPL-3.0-only
 
 /**
@@ -26,7 +25,7 @@
 
 import * as networkLayer from "../npms/skale-cool-socket/socket.mjs";
 import * as url from "url";
-import { Worker } from "worker_threads";
+import * as threadInfo from "./threadInfo.mjs";
 import * as path from "path";
 import * as cc from "../npms/skale-cc/cc.mjs";
 import * as log from "../npms/skale-log/log.mjs";
@@ -133,8 +132,9 @@ export function checkTimeFraming( d, strDirection, joRuntimeOpts ) {
             return false;
     } catch ( err ) {
         if( log.verboseGet() >= log.verboseReversed().error ) {
-            log.write( cc.error( "Exception in time framing check: " ) +
-                cc.error( owaspUtils.extractErrorMessage( err ) ) +
+            log.write( cc.error( "Exception in time framing check in " ) +
+                threadInfo.threadDescription() + cc.error( ": " ) +
+                cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
         }
     }
@@ -145,15 +145,17 @@ async function singleTransferLoopPartOracle( optsLoop, strLogPrefix ) {
     const imaState = state.get();
     let b0 = true;
     if( optsLoop.enableStepOracle && imaOracleOperations.getEnabledOracle() ) {
-        if( log.verboseGet() >= log.verboseReversed().notice )
-            log.write( strLogPrefix + cc.debug( "Will invoke Oracle gas price setup..." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().notice ) {
+            log.write( strLogPrefix + cc.debug( "Will invoke Oracle gas price setup in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
         try {
             if( ! await pwa.checkOnLoopStart( imaState, "oracle" ) ) {
                 imaState.loopState.oracle.wasInProgress = false;
                 if( log.verboseGet() >= log.verboseReversed().notice ) {
-                    log.write( strLogPrefix +
-                        cc.warning( "Skipped(oracle) due to cancel mode reported from PWA" ) +
-                        "\n" );
+                    log.write( strLogPrefix + cc.warning( "Skipped(oracle) in " ) +
+                        threadInfo.threadDescription() +
+                        cc.warning( " due to cancel mode reported from PWA" ) + "\n" );
                 }
             } else {
                 if( checkTimeFraming( null, "oracle", optsLoop.joRuntimeOpts ) ) {
@@ -173,16 +175,17 @@ async function singleTransferLoopPartOracle( optsLoop, strLogPrefix ) {
                     await pwa.notifyOnLoopEnd( imaState, "oracle" );
                 } else {
                     if( log.verboseGet() >= log.verboseReversed().notice ) {
-                        log.write( strLogPrefix +
-                            cc.warning( "Skipped(oracle) due to time framing check" ) +
-                            "\n" );
+                        log.write( strLogPrefix + cc.warning( "Skipped(oracle) in " ) +
+                            threadInfo.threadDescription() +
+                            cc.warning( " due to time framing check" ) + "\n" );
                     }
                 }
             }
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
                 log.write( strLogPrefix + cc.error( "Oracle operation exception: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) ) +
+                    cc.error( " in " ) + threadInfo.threadDescription() +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
             imaState.loopState.oracle.isInProgress = false;
@@ -190,8 +193,8 @@ async function singleTransferLoopPartOracle( optsLoop, strLogPrefix ) {
             throw err;
         }
         if( log.verboseGet() >= log.verboseReversed().information ) {
-            log.write( strLogPrefix + cc.debug( "Oracle gas price setup done: " ) + cc.tf( b0 ) +
-                "\n" );
+            log.write( strLogPrefix + cc.debug( "Oracle gas price setup done in " ) +
+                threadInfo.threadDescription() + cc.debug( ": " ) + cc.tf( b0 ) + "\n" );
         }
     }
     return b0;
@@ -201,14 +204,17 @@ async function singleTransferLoopPartM2S( optsLoop, strLogPrefix ) {
     const imaState = state.get();
     let b1 = true;
     if( optsLoop.enableStepM2S ) {
-        if( log.verboseGet() >= log.verboseReversed().notice )
-            log.write( strLogPrefix + cc.debug( "Will invoke M2S transfer..." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().notice ) {
+            log.write( strLogPrefix + cc.debug( "Will invoke M2S transfer in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
         try {
             if( ! await pwa.checkOnLoopStart( imaState, "m2s" ) ) {
                 imaState.loopState.m2s.wasInProgress = false;
                 if( log.verboseGet() >= log.verboseReversed().notice ) {
-                    log.write( strLogPrefix +
-                        cc.warning( "Skipped(m2s) due to cancel mode reported from PWA" ) + "\n" );
+                    log.write( strLogPrefix + cc.warning( "Skipped(m2s) in " ) +
+                        threadInfo.threadDescription() +
+                        cc.warning( " due to cancel mode reported from PWA" ) + "\n" );
                 }
             } else {
                 if( checkTimeFraming( null, "m2s", optsLoop.joRuntimeOpts ) ) {
@@ -244,26 +250,32 @@ async function singleTransferLoopPartM2S( optsLoop, strLogPrefix ) {
                     await pwa.notifyOnLoopEnd( imaState, "m2s" );
                 } else {
                     if( log.verboseGet() >= log.verboseReversed().notice ) {
-                        log.write( strLogPrefix +
-                            cc.warning( "Skipped(m2s) due to time framing check" ) + "\n" );
+                        log.write( strLogPrefix + cc.warning( "Skipped(m2s) in " ) +
+                            threadInfo.threadDescription() +
+                            cc.warning( " due to time framing check" ) + "\n" );
                     }
                 }
             }
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( strLogPrefix + cc.error( "M2S transfer exception: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                log.write( strLogPrefix + cc.error( "M2S transfer exception in " ) +
+                    threadInfo.threadDescription() + cc.error( ": " ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
             imaState.loopState.m2s.isInProgress = false;
             await pwa.notifyOnLoopEnd( imaState, "m2s" );
             throw err;
         }
-        if( log.verboseGet() >= log.verboseReversed().information )
-            log.write( strLogPrefix + cc.debug( "M2S transfer done: " ) + cc.tf( b1 ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().information ) {
+            log.write( strLogPrefix + cc.debug( "M2S transfer done in " ) +
+                threadInfo.threadDescription() + cc.debug( ": " ) + cc.tf( b1 ) + "\n" );
+        }
     } else {
-        if( log.verboseGet() >= log.verboseReversed().debug )
-            log.write( strLogPrefix + cc.debug( "Skipped M2S transfer." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( strLogPrefix + cc.warning( "Skipped M2S transfer in " ) +
+                threadInfo.threadDescription() + cc.warning( "." ) + "\n" );
+        }
     }
     return b1;
 }
@@ -272,14 +284,17 @@ async function singleTransferLoopPartS2M( optsLoop, strLogPrefix ) {
     const imaState = state.get();
     let b2 = true;
     if( optsLoop.enableStepS2M ) {
-        if( log.verboseGet() >= log.verboseReversed().notice )
-            log.write( strLogPrefix + cc.debug( "Will invoke S2M transfer..." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().notice ) {
+            log.write( strLogPrefix + cc.debug( "Will invoke S2M transfer in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
         try {
             if( ! await pwa.checkOnLoopStart( imaState, "s2m" ) ) {
                 imaState.loopState.s2m.wasInProgress = false;
                 if( log.verboseGet() >= log.verboseReversed().notice ) {
-                    log.write( strLogPrefix +
-                        cc.warning( "Skipped(s2m) due to cancel mode reported from PWA" ) + "\n" );
+                    log.write( strLogPrefix + cc.warning( "Skipped(s2m) in " ) +
+                        threadInfo.threadDescription() +
+                        cc.warning( " due to cancel mode reported from PWA" ) + "\n" );
                 }
             } else {
                 if( checkTimeFraming( null, "s2m", optsLoop.joRuntimeOpts ) ) {
@@ -315,26 +330,32 @@ async function singleTransferLoopPartS2M( optsLoop, strLogPrefix ) {
                     await pwa.notifyOnLoopEnd( imaState, "s2m" );
                 } else {
                     if( log.verboseGet() >= log.verboseReversed().notice ) {
-                        log.write( strLogPrefix +
-                            cc.warning( "Skipped(s2m) due to time framing check" ) + "\n" );
+                        log.write( strLogPrefix + cc.warning( "Skipped(s2m) in " ) +
+                            threadInfo.threadDescription() +
+                            cc.warning( " due to time framing check" ) + "\n" );
                     }
                 }
             }
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( strLogPrefix + cc.error( "S2M transfer exception: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                log.write( strLogPrefix + cc.error( "S2M transfer exception in " ) +
+                    threadInfo.threadDescription() + cc.error( ": " ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
             imaState.loopState.s2m.isInProgress = false;
             await pwa.notifyOnLoopEnd( imaState, "s2m" );
             throw err;
         }
-        if( log.verboseGet() >= log.verboseReversed().information )
-            log.write( strLogPrefix + cc.debug( "S2M transfer done: " ) + cc.tf( b2 ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().information ) {
+            log.write( strLogPrefix + cc.debug( "S2M transfer done in " ) +
+                threadInfo.threadDescription() + cc.debug( ": " ) + cc.tf( b2 ) + "\n" );
+        }
     } else {
-        if( log.verboseGet() >= log.verboseReversed().debug )
-            log.write( strLogPrefix + cc.debug( "Skipped S2M transfer." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( strLogPrefix + cc.warning( "Skipped S2M transfer in " ) +
+                threadInfo.threadDescription() + cc.warning( "." ) + "\n" );
+        }
     }
     return b2;
 }
@@ -366,25 +387,31 @@ async function singleTransferLoopPartS2S( optsLoop, strLogPrefix ) {
             );
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( strLogPrefix + cc.error( "S2S transfer exception: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                log.write( strLogPrefix + cc.error( "S2S transfer exception in " ) +
+                    threadInfo.threadDescription() + cc.error( ": " ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
             throw err;
         }
-        if( log.verboseGet() >= log.verboseReversed().information )
-            log.write( strLogPrefix + cc.debug( "All S2S transfers done: " ) + cc.tf( b3 ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().information ) {
+            log.write( strLogPrefix + cc.debug( "All S2S transfers done in " ) +
+                threadInfo.threadDescription() + cc.debug( ": " ) + cc.tf( b3 ) + "\n" );
+        }
 
     } else {
-        if( log.verboseGet() >= log.verboseReversed().debug )
-            log.write( strLogPrefix + cc.debug( "Skipped S2S transfer." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( strLogPrefix + cc.warning( "Skipped S2S transfer in " ) +
+                threadInfo.threadDescription() + cc.warning( "." ) + "\n" );
+        }
     }
     return b3;
 }
 
 export async function singleTransferLoop( optsLoop ) {
     const imaState = state.get();
-    const strLogPrefix = cc.attention( "Single Loop:" ) + " ";
+    const strLogPrefix = cc.attention( "Single Loop in " ) +
+        threadInfo.threadDescription() + cc.debug( ":" ) + " ";
     try {
         if( log.verboseGet() >= log.verboseReversed().debug )
             log.write( strLogPrefix + cc.debug( imaHelperAPIs.longSeparator ) + "\n" );
@@ -414,7 +441,7 @@ export async function singleTransferLoop( optsLoop ) {
     } catch ( err ) {
         if( log.verboseGet() >= log.verboseReversed().error ) {
             log.write( strLogPrefix + cc.fatal( "Exception in single transfer loop: " ) +
-                cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
         }
     }
@@ -445,15 +472,24 @@ export async function runTransferLoop( optsLoop ) {
 
 // Parallel thread based loop
 
-const sleepImpl = ( milliseconds ) => {
-    return new Promise( resolve => setTimeout( resolve, milliseconds ) );
-};
-
 const gArrWorkers = [];
 const gArrClients = [];
 
 export function notifyCacheChangedSNB( arrSChainsCached ) {
     const cntWorkers = gArrWorkers.length;
+    if( cntWorkers == 0 ) {
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( cc.warning( "Will skip chainsCacheChanged dispatch event with " ) +
+                cc.warning( "no chains arrived in " ) + threadInfo.threadDescription() + "\n" );
+        }
+        return;
+    }
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        log.write(
+            cc.debug( "Loop module will broadcast arrSChainsCached event to its " ) +
+            cc.info( cntWorkers ) + cc.debug( " worker(s) in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+    }
     for( let idxWorker = 0; idxWorker < cntWorkers; ++ idxWorker ) {
         const jo = {
             "method": "schainsCached",
@@ -461,11 +497,35 @@ export function notifyCacheChangedSNB( arrSChainsCached ) {
                 "arrSChainsCached": arrSChainsCached
             }
         };
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( cc.debug( "S-Chains cache will be sent to " ) +
+                cc.notice( gArrClients[idxWorker].url ) + cc.debug( " loop worker..." ) +
+                "\n" );
+        }
         gArrClients[idxWorker].send( jo );
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( cc.debug( "S-Chains cache did sent to " ) +
+                cc.notice( gArrClients[idxWorker].url ) + cc.debug( " loop worker" ) +
+                "\n" );
+        }
+    }
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        log.write(
+            cc.debug( "Loop module did finished broadcasting arrSChainsCached event to its " ) +
+            cc.info( cntWorkers ) + cc.debug( " worker(s) in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
     }
 }
 
+if( log.verboseGet() >= log.verboseReversed().trace ) {
+    log.write( cc.debug( "Subscribe to chainsCacheChanged event in " ) +
+        threadInfo.threadDescription() + "\n" );
+}
 skaleObserver.events.on( "chainsCacheChanged", function( eventData ) {
+    if( log.verboseGet() >= log.verboseReversed().trace ) {
+        log.write( cc.debug( "Did arrived chainsCacheChanged event in " ) +
+            threadInfo.threadDescription() + "\n" );
+    }
     notifyCacheChangedSNB( eventData.detail.arrSChainsCached );
 } );
 
@@ -565,13 +625,17 @@ export async function ensureHaveWorkers( opts ) {
     if( gArrWorkers.length > 0 )
         return gArrWorkers;
     const cntWorkers = 2;
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        log.write( cc.debug( "Loop module will create its " ) +
+            cc.info( cntWorkers ) + cc.debug( " worker(s) in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+    }
     for( let idxWorker = 0; idxWorker < cntWorkers; ++ idxWorker ) {
         const workerData = {
-            url: "ima_loop_server" + idxWorker,
-            cc: { isEnabled: cc.isEnabled() }
+            url: "ima_loop_server" + idxWorker, cc: { isEnabled: cc.isEnabled() }
         };
         gArrWorkers.push(
-            new Worker(
+            new threadInfo.Worker(
                 path.join( __dirname, "loopWorker.mjs" ),
                 { "type": "module", "workerData": workerData }
             )
@@ -580,36 +644,30 @@ export async function ensureHaveWorkers( opts ) {
             if( networkLayer.outOfWorkerAPIs.onMessage( gArrWorkers[idxWorker], jo ) )
                 return;
         } );
-        gArrClients.push(
-            new networkLayer.OutOfWorkerSocketClientPipe(
-                workerData.url, gArrWorkers[idxWorker] )
-        );
+        gArrClients.push( new networkLayer.OutOfWorkerSocketClientPipe(
+            workerData.url, gArrWorkers[idxWorker] ) );
         gArrClients[idxWorker].on( "message", async function( eventData ) {
             const joMessage = eventData.message;
             switch ( joMessage.method ) {
             case "log":
                 log.write( cc.attention( "LOOP WORKER" ) +
-                    " " + cc.notice( workerData.url ) + " " + joMessage.message + "\n"
-                );
+                    " " + cc.notice( workerData.url ) + " " + joMessage.message + "\n" );
                 break;
             case "saveTransferError":
                 imaTransferErrorHandling.saveTransferError(
                     joMessage.message.category,
                     joMessage.message.textLog,
-                    joMessage.message.ts
-                );
+                    joMessage.message.ts );
                 break;
             case "saveTransferSuccess":
                 imaTransferErrorHandling.saveTransferSuccess( joMessage.message.category );
                 break;
             } // switch ( joMessage.method )
         } );
-        await sleepImpl( 3 * 1000 );
+        await threadInfo.sleep( 3 * 1000 );
         const optsLoop = {
             joRuntimeOpts: {
-                isInsideWorker: true,
-                idxChainKnownForS2S: 0,
-                cntChainsKnownForS2S: 0
+                isInsideWorker: true, idxChainKnownForS2S: 0, cntChainsKnownForS2S: 0
             },
             isDelayFirstRun: false,
             enableStepOracle: ( idxWorker == 0 ) ? true : false,
@@ -732,21 +790,42 @@ export async function ensureHaveWorkers( opts ) {
                         "isCrossImaBlsMode": opts.imaState.isCrossImaBlsMode
                     }
                 },
-                "cc": {
-                    "isEnabled": cc.isEnabled()
-                }
+                "cc": { "isEnabled": cc.isEnabled() }
             }
         };
         gArrClients[idxWorker].send( jo );
     }
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        log.write( cc.debug( "Loop module did created its " ) +
+            cc.info( gArrWorkers.length ) + cc.debug( " worker(s) in " ) +
+            threadInfo.threadDescription() + cc.debug( "" ) + "\n" );
+    }
+    if( log.verboseGet() >= log.verboseReversed().trace ) {
+        log.write( cc.debug( "Subscribe to inThread-arrSChainsCached event in " ) +
+            threadInfo.threadDescription() + "\n" );
+    }
+    skaleObserver.events.on( "inThread-arrSChainsCached", function( eventData ) {
+        if( log.verboseGet() >= log.verboseReversed().trace ) {
+            log.write( cc.debug( "Did arrived inThread-arrSChainsCached event in " ) +
+                threadInfo.threadDescription() + "\n" );
+        }
+        if( threadInfo.isMainThread() )
+            notifyCacheChangedSNB( eventData.detail.arrSChainsCached );
+    } );
 }
 
 export async function runParallelLoops( opts ) {
-    if( log.verboseGet() >= log.verboseReversed().notice )
-        log.write( cc.debug( "Will start parallel IMA transfer loops..." ) + "\n" );
+    if( log.verboseGet() >= log.verboseReversed().notice ) {
+        log.write( cc.debug( "Will start parallel IMA transfer loops in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+    }
     await ensureHaveWorkers( opts );
-    if( log.verboseGet() >= log.verboseReversed().notice )
-        log.write( cc.success( "Done, did parallel IMA transfer loops." ) + "\n" );
+    if( log.verboseGet() >= log.verboseReversed().notice ) {
+        log.write( cc.success( "Done, did started parallel IMA transfer loops in " ) +
+            threadInfo.threadDescription() + cc.debug( ", have " ) +
+            cc.info( gArrWorkers.length ) + cc.success( " worker(s) and " ) +
+            cc.info( gArrClients.length ) + cc.success( " clients(s)." ) + "\n" );
+    }
     return true;
 }
 
diff --git a/agent/loopWorker.mjs b/agent/loopWorker.mjs
index f8458424f..b0c66e2ce 100644
--- a/agent/loopWorker.mjs
+++ b/agent/loopWorker.mjs
@@ -1,4 +1,3 @@
-
 // SPDX-License-Identifier: AGPL-3.0-only
 
 /**
@@ -37,6 +36,7 @@ import * as imaCLI from "./cli.mjs";
 import * as state from "./state.mjs";
 import * as pwa from "./pwa.mjs";
 import * as log from "../npms/skale-log/log.mjs";
+import * as threadInfo from "./threadInfo.mjs";
 
 let imaState = state.get();
 
@@ -101,6 +101,11 @@ class ObserverServer extends SocketServer {
                 const isFlush = true;
                 socket.send( jo, isFlush );
             } );
+            if( log.verboseGet() >= log.verboseReversed().debug ) {
+                log.write(
+                    cc.debug( "Loop worker " ) + cc.notice( workerData.url ) +
+                    cc.debug( " will save cached S-Chains..." ) + "\n" );
+            }
             skaleObserver.setLastCachedSChains( self.opts.imaState.arrSChainsCached );
             joAnswer.message = {
                 "method": "" + joMessage.method,
@@ -120,7 +125,8 @@ class ObserverServer extends SocketServer {
                     self.log( cc.warning( "WARNING:" ) + cc.warning( " No " ) +
                         cc.note( "Main-net" ) +
                         cc.warning( " URL specified in command line arguments" ) +
-                        cc.debug( "(needed for particular operations only)" ) + "\n" );
+                        cc.debug( "(needed for particular operations only) in " ) +
+                        threadInfo.threadDescription() + "\n" );
                 }
             }
 
@@ -136,7 +142,8 @@ class ObserverServer extends SocketServer {
                     self.log( cc.warning( "WARNING:" ) + cc.warning( " No " ) +
                         cc.note( "Main-net" ) +
                         cc.warning( " URL specified in command line arguments" ) +
-                        cc.debug( "(needed for particular operations only)" ) + "\n" );
+                        cc.debug( "(needed for particular operations only) in " ) +
+                        threadInfo.threadDescription() + "\n" );
                 }
             }
 
@@ -169,11 +176,18 @@ class ObserverServer extends SocketServer {
             loop.runTransferLoop( self.opts.imaState.optsLoop );
             if( log.verboseGet() >= log.verboseReversed().information ) {
                 self.log( cc.debug( "Full init compete for in-worker IMA loop" ) +
-                    " " + cc.notice( workerData.url ) + "\n" );
+                    " " + cc.notice( workerData.url ) + cc.debug( " in " ) +
+                    threadInfo.threadDescription() + "\n" );
             }
             return joAnswer;
         };
         self.mapApiHandlers.schainsCached = function( joMessage, joAnswer, eventData, socket ) {
+            if( log.verboseGet() >= log.verboseReversed().debug ) {
+                self.log( cc.debug( "S-Chains cache did arrived to " ) +
+                    cc.notice( workerData.url ) + cc.debug( " loop worker in " ) +
+                    threadInfo.threadDescription() + cc.debug( ": " ) +
+                    cc.j( joMessage.message.arrSChainsCached ) + "\n" );
+            }
             skaleObserver.setLastCachedSChains( joMessage.message.arrSChainsCached );
         };
         // eslint-disable-next-line dot-notation
@@ -191,7 +205,8 @@ class ObserverServer extends SocketServer {
             };
         if( log.verboseGet() >= log.verboseReversed().information ) {
             self.log( cc.debug( "Initialized in-worker IMA loop " ) +
-                cc.info( workerData.url ) + cc.debug( " server" ) + "\n" );
+                cc.info( workerData.url ) + cc.debug( " server in " ) +
+                threadInfo.threadDescription() + "\n" );
         }
     }
     dispose() {
@@ -210,7 +225,8 @@ const server = new ObserverServer( acceptor );
 server.on( "dispose", function() {
     const self = server;
     if( log.verboseGet() >= log.verboseReversed().debug ) {
-        self.log( cc.debug( "Disposed in-worker IMA loop" ) +
+        self.log( cc.debug( "Disposed in-worker in " ) +
+        threadInfo.threadDescription() + cc.debug( " IMA loop" ) +
         " " + cc.notice( workerData.url ) + "\n" );
     }
 } );
diff --git a/agent/main.mjs b/agent/main.mjs
index d7a994e9a..9e7126485 100644
--- a/agent/main.mjs
+++ b/agent/main.mjs
@@ -40,6 +40,7 @@ import * as imaBLS from "./bls.mjs";
 import * as pwa from "./pwa.mjs";
 import * as clpTools from "./clpTools.mjs";
 import * as discoveryTools from "./discoveryTools.mjs";
+import * as skaleObserver from "../npms/skale-observer/observer.mjs";
 
 import * as state from "./state.mjs";
 
@@ -364,6 +365,30 @@ function initJsonRpcServer() {
                 ) )
                     await loop.spreadArrivedStateOfPendingWorkAnalysis( joMessage );
 
+                break;
+            case "skale_getCachedSNB":
+                joAnswer.arrSChainsCached = skaleObserver.getLastCachedSChains();
+                break;
+            case "skale_historySNB":
+                joAnswer.arrCacheHistory = skaleObserver.getLastCachedHistory();
+                break;
+            case "skale_refreshSNB":
+                {
+                    const opts = {
+                        imaState: imaState,
+                        "details": log,
+                        "bStopNeeded": false,
+                        "secondsToReDiscoverSkaleNetwork":
+                            imaState.optsS2S.secondsToReDiscoverSkaleNetwork,
+                        "secondsToWaitForSkaleNetworkDiscovered":
+                            imaState.optsS2S.secondsToWaitForSkaleNetworkDiscovered,
+                        "chain": imaState.chainProperties.sc,
+                        "bParallelModeRefreshSNB":
+                            imaState.optsS2S.bParallelModeRefreshSNB ? true : false
+                    };
+                    skaleObserver.refreshNowSNB( opts ); // async call, no await here
+                    joAnswer.result = "Done";
+                }
                 break;
             default:
                 throw new Error( "Unknown method name \"" + joMessage.method + "\" was specified" );
@@ -418,7 +443,7 @@ async function doTheJob() {
             if( log.verboseGet() >= log.verboseReversed().critical ) {
                 log.write( strLogPrefix + cc.fatal( "CRITICAL ERROR:" ) +
                     cc.error( " Exception occurred while executing action: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
         }
diff --git a/agent/state.mjs b/agent/state.mjs
index 9739a7bfb..fa0c6357d 100644
--- a/agent/state.mjs
+++ b/agent/state.mjs
@@ -274,7 +274,7 @@ export function get() {
         "optsS2S": {
             // is S-Chain to S-Chain transfers enabled
             "isEnabled": true,
-            "bParallelModeRefreshSNB": false,
+            "bParallelModeRefreshSNB": true,
             // seconds to re-discover SKALE network, 0 to disable
             "secondsToReDiscoverSkaleNetwork": 1 * 60 * 60,
             "secondsToWaitForSkaleNetworkDiscovered": 2 * 60
diff --git a/agent/threadInfo.mjs b/agent/threadInfo.mjs
new file mode 100644
index 000000000..6a94cbc02
--- /dev/null
+++ b/agent/threadInfo.mjs
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ * @license
+ * SKALE IMA
+ *
+ * SKALE IMA is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SKALE IMA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with SKALE IMA.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file loop.mjs
+ * @copyright SKALE Labs 2019-Present
+ */
+
+import * as worker_threads from "worker_threads";
+import * as cc from "../npms/skale-cc/cc.mjs";
+
+const Worker = worker_threads.Worker;
+export { Worker };
+
+export const sleep = ( milliseconds ) => {
+    return new Promise( resolve => setTimeout( resolve, milliseconds ) );
+};
+
+export function getCurrentThreadID() {
+    return worker_threads.threadId;
+}
+
+export function isMainThread() {
+    return worker_threads.isMainThread ? true : false;
+}
+
+export function threadDescription( isColorized ) {
+    if( typeof isColorized == "undefined" )
+        isColorized = true;
+    const tid = getCurrentThreadID();
+    const st = isMainThread() ? "main" : "worker";
+    return isColorized
+        ? ( cc.sunny( st ) + cc.bright( " thread " ) + cc.info( tid ) )
+        : ( st + " thread " + tid );
+}
diff --git a/npms/skale-ima/imaEventLogScan.mjs b/npms/skale-ima/imaEventLogScan.mjs
index 14c069d0a..36f4e1a19 100644
--- a/npms/skale-ima/imaEventLogScan.mjs
+++ b/npms/skale-ima/imaEventLogScan.mjs
@@ -104,12 +104,13 @@ export async function safeGetPastEventsProgressive(
             nBlockFrom, nBlockTo, joFilter
         );
     }
-    const nLatestBlockNumber =
-        owaspUtils.toBN( await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumber = owaspUtils.toBN(
+        await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) );
     let isLastLatest = false;
     if( nBlockTo == "latest" ) {
         isLastLatest = true;
-        nBlockTo = nLatestBlockNumber;
+        nBlockTo = nLatestBlockNumberPlus1;
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             details.write( strLogPrefix + cc.debug( "Iterative scan up to latest block " ) +
                 cc.attention( "#" ) + cc.info( nBlockTo.toHexString() ) +
@@ -117,7 +118,7 @@ export async function safeGetPastEventsProgressive(
         }
     } else {
         nBlockTo = owaspUtils.toBN( nBlockTo );
-        if( nBlockTo.eq( nLatestBlockNumber ) )
+        if( nBlockTo.gte( nLatestBlockNumber ) )
             isLastLatest = true;
     }
     nBlockFrom = owaspUtils.toBN( nBlockFrom );
@@ -143,7 +144,7 @@ export async function safeGetPastEventsProgressive(
             cc.info( nLatestBlockNumber.toHexString() ) + "\n" );
     }
     const arrProgressiveEventsScanPlan =
-        createProgressiveEventsScanPlan( details, nLatestBlockNumber );
+        createProgressiveEventsScanPlan( details, nLatestBlockNumberPlus1 );
     if( log.verboseGet() >= log.verboseReversed().trace ) {
         details.write( cc.debug( "Composed " ) + cc.attention( "progressive" ) +
             cc.debug( " scan plan is: " ) + cc.j( arrProgressiveEventsScanPlan ) + "\n" );
@@ -184,7 +185,7 @@ export async function safeGetPastEventsProgressive(
         } catch ( err ) {}
     }
     if( log.verboseGet() >= log.verboseReversed().error ) {
-        details.write( strLogPrefix + cc.error( "Could not not get Event \"" ) +
+        details.write( strLogPrefix + cc.error( "Could not get Event \"" ) +
             cc.info( strEventName ) + cc.error( "\", from block " ) +
             cc.info( joLastPlan.nBlockFrom ) + cc.error( ", to block " ) +
             cc.info( joLastPlan.nBlockTo ) + cc.debug( ", block range is " ) +
@@ -204,12 +205,13 @@ export async function getContractCallEvents(
     const n10 = owaspUtils.toBN( 10 );
     let nBlockFrom = nBlockNumber.sub( n10 ), nBlockTo = nBlockNumber.add( n10 );
     const nBlockZero = owaspUtils.toBN( 0 );
-    const nLatestBlockNumber =
-        owaspUtils.toBN( await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumber = owaspUtils.toBN(
+        await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) );
     if( nBlockFrom.lt( nBlockZero ) )
         nBlockFrom = nBlockZero;
-    if( nBlockTo.gt( nLatestBlockNumber ) )
-        nBlockTo = nLatestBlockNumber;
+    if( nBlockTo.gte( nLatestBlockNumber ) )
+        nBlockTo = nLatestBlockNumberPlus1;
     const joAllEventsInBlock =
         await safeGetPastEventsIterative(
             details, strLogPrefix, ethersProvider, 10, joContract, strEventName,
@@ -291,7 +293,7 @@ export async function safeGetTransactionCount(
     if( ( idxAttempt + 1 ) > cntAttempts && ret === "" ) {
         if( log.verboseGet() >= log.verboseReversed().error ) {
             details.write( cc.fatal( "ERROR:" ) + cc.error( " Failed call to " ) +
-                cc.note( strFnName + "()" ) + + cc.error( " via " ) + cc.u( u ) +
+                cc.note( strFnName + "()" ) + cc.error( " via " ) + cc.u( u ) +
                 cc.error( " after " ) + cc.info( cntAttempts ) + cc.error( " attempts " ) + "\n" );
         }
         throw new Error(
@@ -400,7 +402,8 @@ export async function safeGetPastEvents(
     if( nBlockTo == "latest" ) {
         const nLatestBlockNumber = owaspUtils.toBN(
             await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
-        nBlockTo = nLatestBlockNumber;
+        const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) );
+        nBlockTo = nLatestBlockNumberPlus1;
     } else
         nBlockTo = owaspUtils.toBN( nBlockTo );
     nBlockFrom = owaspUtils.toBN( nBlockFrom );
@@ -541,12 +544,13 @@ export async function safeGetPastEventsIterative(
             strEventName, nBlockFrom, nBlockTo, joFilter
         );
     }
-    const nLatestBlockNumber =
-        owaspUtils.toBN( await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumber = owaspUtils.toBN(
+        await imaHelperAPIs.safeGetBlockNumber( details, 10, ethersProvider ) );
+    const nLatestBlockNumberPlus1 = nLatestBlockNumber.add( owaspUtils.toBN( 1 ) );
     let isLastLatest = false;
     if( nBlockTo == "latest" ) {
         isLastLatest = true;
-        nBlockTo = nLatestBlockNumber;
+        nBlockTo = nLatestBlockNumberPlus1;
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             details.write( strLogPrefix + cc.debug( "Iterative scan up to latest block " ) +
                 cc.info( "#" ) + cc.info( nBlockTo.toHexString() ) +
@@ -554,7 +558,7 @@ export async function safeGetPastEventsIterative(
         }
     } else {
         nBlockTo = owaspUtils.toBN( nBlockTo );
-        if( nBlockTo.eq( nLatestBlockNumber ) )
+        if( nBlockTo.gte( nLatestBlockNumber ) )
             isLastLatest = true;
     }
     nBlockFrom = owaspUtils.toBN( nBlockFrom );
@@ -584,12 +588,12 @@ export async function safeGetPastEventsIterative(
             cc.info( nBlockFrom.toHexString() ) + cc.debug( "/" ) +
             cc.info( nBlockTo.toHexString() ) + cc.debug( " block range..." ) + "\n" );
     }
-    let idxBlockSubRangeFrom = nBlockFrom;
+    let idxBlockSubRangeTo = nBlockTo;
     for( ; true; ) {
-        let idxBlockSubRangeTo = idxBlockSubRangeFrom.add(
+        let idxBlockSubRangeFrom = idxBlockSubRangeTo.sub(
             owaspUtils.toBN( imaHelperAPIs.getBlocksCountInInIterativeStepOfEventsScan() ) );
-        if( idxBlockSubRangeTo.gt( nBlockTo ) )
-            idxBlockSubRangeTo = nBlockTo;
+        if( idxBlockSubRangeFrom.lt( nBlockFrom ) )
+            idxBlockSubRangeFrom = nBlockFrom;
         try {
             if( log.verboseGet() >= log.verboseReversed().trace ) {
                 details.write( strLogPrefix + cc.debug( "Iterative scan of " ) +
@@ -627,8 +631,8 @@ export async function safeGetPastEventsIterative(
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
         }
-        idxBlockSubRangeFrom = idxBlockSubRangeTo;
-        if( idxBlockSubRangeFrom.eq( nBlockTo ) )
+        idxBlockSubRangeTo = idxBlockSubRangeFrom;
+        if( idxBlockSubRangeTo.lte( nBlockFrom ) )
             break;
     }
     if( log.verboseGet() >= log.verboseReversed().debug ) {
diff --git a/npms/skale-ima/imaSgxExternalSigner.mjs b/npms/skale-ima/imaSgxExternalSigner.mjs
index 1231b09d0..87715dcc4 100644
--- a/npms/skale-ima/imaSgxExternalSigner.mjs
+++ b/npms/skale-ima/imaSgxExternalSigner.mjs
@@ -26,7 +26,7 @@ function postConvertBN( jo, name ) {
         return;
     if( typeof jo[name] != "object" )
         return;
-    jo[name] = jo[name].toHexString();
+    jo[name] = owaspUtils.toHexStringSafe( jo[name] );
 }
 
 async function run() {
diff --git a/npms/skale-ima/imaTx.mjs b/npms/skale-ima/imaTx.mjs
index d5503c5d1..b0b80c7e8 100644
--- a/npms/skale-ima/imaTx.mjs
+++ b/npms/skale-ima/imaTx.mjs
@@ -193,7 +193,7 @@ async function payedCallTM( optsPayedCall ) {
                     typeof txAdjusted[strName] == "object" &&
                     typeof txAdjusted[strName].toHexString == "function"
                 )
-                    txAdjusted[strName] = txAdjusted[strName].toHexString();
+                    txAdjusted[strName] = owaspUtils.toHexStringSafe( txAdjusted[strName] );
             }
             if( "gasLimit" in txAdjusted )
                 delete txAdjusted.gasLimit;
@@ -261,10 +261,10 @@ async function payedCallSGX( optsPayedCall ) {
         "\"" + chainId + "\" " +
         "\"" + ( tx.data ? tx.data : "" ) + "\" " +
         "\"" + tx.to + "\" " +
-        "\"" + ( tx.value ? tx.value.toHexString() : "0" ) + "\" " +
-        "\"" + tx.gasPrice.toHexString() + "\" " +
-        "\"" + tx.gasLimit.toHexString() + "\" " +
-        "\"" + tx.nonce.toHexString() + "\" " +
+        "\"" + owaspUtils.toHexStringSafe( tx.value ) + "\" " +
+        "\"" + owaspUtils.toHexStringSafe( tx.gasPrice ) + "\" " +
+        "\"" + owaspUtils.toHexStringSafe( tx.gasLimit ) + "\" " +
+        "\"" + owaspUtils.toHexStringSafe( tx.nonce ) + "\" " +
         "\"" + ( optsPayedCall.joAccount.strPathSslCert
         ? optsPayedCall.joAccount.strPathSslCert : "" ) + "\" " +
         "\"" + ( optsPayedCall.joAccount.strPathSslKey
@@ -482,22 +482,25 @@ export async function checkTransactionToSchain(
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             details.write( strLogPrefix + cc.debug( "Will check whether PoW-mining " +
                 "is needed for sender " ) + cc.notice( strFromAddress ) +
-                cc.debug( " with balance " ) + cc.info( balance.toHexString() ) +
-                cc.debug( " using required balance " ) + cc.info( requiredBalance.toHexString() ) +
-                cc.debug( ", gas limit is " ) + cc.info( unsignedTx.gasLimit.toHexString() ) +
+                cc.debug( " with balance " ) + cc.info( owaspUtils.toHexStringSafe( balance ) ) +
+                cc.debug( " using required balance " ) +
+                cc.info( owaspUtils.toHexStringSafe( requiredBalance ) ) +
+                cc.debug( ", gas limit is " ) +
+                cc.info( owaspUtils.toHexStringSafe( unsignedTx.gasLimit ) ) +
                 cc.debug( " gas, checked unsigned transaction is " ) + cc.j( unsignedTx ) + "\n" );
         }
         if( balance.lt( requiredBalance ) ) {
             if( log.verboseGet() >= log.verboseReversed().trace ) {
                 details.write( strLogPrefix + cc.warning( "Insufficient funds for " ) +
                     cc.notice( strFromAddress ) + cc.warning( ", will run PoW-mining to get " ) +
-                    cc.info( unsignedTx.gasLimit.toHexString() ) + cc.warning( " of gas" ) + "\n" );
+                    cc.info( owaspUtils.toHexStringSafe( unsignedTx.gasLimit ) ) +
+                    cc.warning( " of gas" ) + "\n" );
             }
             let powNumber =
                 await calculatePowNumber(
                     strFromAddress,
                     owaspUtils.toBN( unsignedTx.nonce ).toHexString(),
-                    unsignedTx.gasLimit.toHexString(),
+                    owaspUtils.toHexStringSafe( unsignedTx.gasLimit ),
                     details,
                     strLogPrefix
                 );
@@ -544,7 +547,7 @@ export async function checkTransactionToSchain(
             details.write( strLogPrefix +
                 cc.fatal( "CRITICAL PoW-mining ERROR(checkTransactionToSchain):" ) + " " +
                 cc.error( "exception occur before PoW-mining, error is:" ) + " " +
-                cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
         }
     }
@@ -575,7 +578,7 @@ export async function calculatePowNumber( address, nonce, gas, details, strLogPr
             details.write( strLogPrefix +
                 cc.fatal( "CRITICAL PoW-mining ERROR(calculatePowNumber):" ) + " " +
                 cc.error( "exception occur during PoW-mining, error is:" ) + " " +
-                cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) +
                 "\n" );
         }
diff --git a/npms/skale-ima/index.mjs b/npms/skale-ima/index.mjs
index b3b266cbf..d4bd0fcd7 100644
--- a/npms/skale-ima/index.mjs
+++ b/npms/skale-ima/index.mjs
@@ -35,6 +35,7 @@ import * as imaTx from "./imaTx.mjs";
 import * as imaGasUsage from "./imaGasUsageOperations.mjs";
 import * as imaEventLogScan from "./imaEventLogScan.mjs";
 import * as imaTransferErrorHandling from "./imaTransferErrorHandling.mjs";
+import * as threadInfo from "../../agent/threadInfo.mjs";
 
 cc.enable( false );
 log.addStdout();
@@ -1233,7 +1234,7 @@ async function doMainTransferLoopActions( optsTransfer ) {
                 const strError =
                     optsTransfer.strLogPrefix + cc.fatal( "CRITICAL ERROR:" ) +
                     cc.error( " Exception from signing messages function: " ) +
-                    cc.error( owaspUtils.extractErrorMessage( err ) +
+                    cc.warning( owaspUtils.extractErrorMessage( err ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) +
                     "\n" );
                 optsTransfer.details.write( strError );
@@ -1387,7 +1388,7 @@ export async function doTransfer(
             const strError = optsTransfer.strLogPrefix + cc.fatal( "CRITICAL ERROR:" ) +
                 cc.error( " Error in " ) + optsTransfer.strGatheredDetailsName_colored +
                 cc.error( " during " + optsTransfer.strActionName + ": " ) +
-                cc.error( owaspUtils.extractErrorMessage( err ) ) +
+                cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) +
                 "\n";
             optsTransfer.details.write( strError );
@@ -1439,7 +1440,8 @@ export async function doAllS2S( // s-chain --> s-chain
     const cntSChains = arrSChainsCached.length;
     if( log.verboseGet() >= log.verboseReversed().information ) {
         log.write( cc.debug( "Have " ) + cc.info( cntSChains ) +
-            cc.debug( " S-Chain(s) connected to this S-Chain for performing S2S transfers." ) +
+            cc.debug( " S-Chain(s) connected to this S-Chain for performing S2S transfers in " ) +
+            threadInfo.threadDescription() + cc.debug( "." ) +
             "\n" );
     }
     for( let idxSChain = 0; idxSChain < cntSChains; ++ idxSChain ) {
@@ -1451,7 +1453,8 @@ export async function doAllS2S( // s-chain --> s-chain
         const chainIdSrc = "" + joSChain.data.computed.chainId;
         if( log.verboseGet() >= log.verboseReversed().information ) {
             log.write( cc.debug( "S2S transfer walk trough " ) + cc.info( chainNameSrc ) +
-                cc.debug( "/" ) + cc.info( chainIdSrc ) + cc.debug( " S-Chain..." ) + "\n" );
+                cc.debug( "/" ) + cc.info( chainIdSrc ) + cc.debug( " S-Chain in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
         }
         let bOK = false;
         try {
@@ -1459,8 +1462,9 @@ export async function doAllS2S( // s-chain --> s-chain
             if( ! await pwa.checkOnLoopStart( imaState, "s2s", nIndexS2S ) ) {
                 imaState.loopState.s2s.wasInProgress = false;
                 if( log.verboseGet() >= log.verboseReversed().notice ) {
-                    log.write( cc.warning( "Skipped(s2s) due to cancel mode reported from PWA" ) +
-                        "\n" );
+                    log.write(
+                        cc.warning( "Skipped(s2s) due to cancel mode reported from PWA in " ) +
+                        threadInfo.threadDescription() + cc.debug( "" ) + "\n" );
                 }
             } else {
                 if( loop.checkTimeFraming( null, "s2s", joRuntimeOpts ) ) {
@@ -1526,8 +1530,9 @@ export async function doAllS2S( // s-chain --> s-chain
                     bOK = true;
                     if( log.verboseGet() >= log.verboseReversed().notice ) {
                         const strLogPrefix = cc.attention( "S2S Loop:" ) + " ";
-                        log.write( strLogPrefix +
-                            cc.warning( "Skipped(s2s) due to time framing check" ) + "\n" );
+                        log.write( strLogPrefix + cc.warning( "Skipped(s2s) in " ) +
+                            threadInfo.threadDescription() +
+                            cc.debug( " due to time framing check" ) + "\n" );
                     }
                 }
             }
@@ -1537,6 +1542,7 @@ export async function doAllS2S( // s-chain --> s-chain
                 const strError = owaspUtils.extractErrorMessage( err );
                 log.write( cc.fatal( "S2S STEP ERROR:" ) + cc.error( " From S-Chain " ) +
                     cc.info( chainNameSrc ) + cc.error( ", error is: " ) + cc.warning( strError ) +
+                    cc.error( " in " ) + threadInfo.threadDescription() +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
             imaState.loopState.s2s.isInProgress = false;
@@ -1552,14 +1558,15 @@ export async function doAllS2S( // s-chain --> s-chain
     if( "joExtraSignOpts" in joRuntimeOpts )
         delete joRuntimeOpts.joExtraSignOpts; // reset/clear
     if( log.verboseGet() >= log.verboseReversed().debug && ( cntOK > 0 || cntFail > 0 ) ) {
-        let s = cc.debug( "Stats for S2S steps:" );
+        let s = cc.debug( "Stats for S2S steps in " ) +
+            threadInfo.threadDescription() + cc.debug( ": " );
         if( cntOK > 0 ) {
             s += " " + cc.info( cntOK ) +
-                cc.success( " S-Chain(s) processed OKay" ) + cc.debug( ", " );
+                cc.success( "S-Chain(s) processed OKay" ) + cc.debug( ", " );
         }
         if( cntFail > 0 ) {
             s += " " + cc.info( cntFail ) +
-                cc.error( " S-Chain(s) failed" );
+                cc.error( "S-Chain(s) failed" );
         }
         log.write( s + "\n" );
     }
diff --git a/npms/skale-observer/observer.mjs b/npms/skale-observer/observer.mjs
index 6af40d319..68384cfeb 100644
--- a/npms/skale-observer/observer.mjs
+++ b/npms/skale-observer/observer.mjs
@@ -26,7 +26,7 @@
 import * as path from "path";
 import * as url from "url";
 import * as networkLayer from "../skale-cool-socket/socket.mjs";
-import { Worker } from "worker_threads";
+import * as threadInfo from "../../agent/threadInfo.mjs";
 import * as owaspUtils from "../skale-owasp/owaspUtils.mjs";
 import * as cc from "../skale-cc/cc.mjs";
 import * as log from "../skale-log/log.mjs";
@@ -56,7 +56,8 @@ export function getSChainIndexInNode( computedSChainId, arrChainIdsOnNode ) {
     }
     throw new Error(
         "S-Chain " + computedSChainId + " is not found in the list: " +
-        JSON.stringify( arrChainIdsOnNode ) );
+        JSON.stringify( arrChainIdsOnNode ) + "in " +
+        threadInfo.threadDescription( false ) );
 }
 
 export function getSChainBasePortOnNode( computedSChainId, arrChainIdsOnNode, basePortOfNode ) {
@@ -176,10 +177,12 @@ async function isMulticallAvailable( mn ) {
 }
 
 // see https://github.com/skalenetwork/skale-proxy/blob/develop/endpoints.py
-export async function loadSChainParts( joSChain, addressFrom, opts ) {
+export async function loadSChainParts( joSChain, opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chain parts in observer, no imaState is provided" );
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot load S-Chain parts in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     let isEMC = false;
     if( opts.imaState.isEnabledMultiCall )
         isEMC = await isMulticallAvailable( opts.imaState.chainProperties.mn );
@@ -187,9 +190,7 @@ export async function loadSChainParts( joSChain, addressFrom, opts ) {
     const computedSChainId = owaspUtils.ethersMod.ethers.utils.id( joSChain.data.name );
     const chainId = owaspUtils.computeChainIdFromSChainName( joSChain.data.name );
     const arrNodeIds =
-        await opts.imaState.joSChainsInternal.callStatic.getNodesInGroup(
-            computedSChainId,
-            { from: addressFrom } );
+        await opts.imaState.joSChainsInternal.callStatic.getNodesInGroup( computedSChainId );
     const nodes = [];
     if( isEMC ) {
         const multicall = new EMC.Multicall( {
@@ -286,24 +287,21 @@ export async function loadSChainParts( joSChain, addressFrom, opts ) {
             if( opts && opts.bStopNeeded )
                 return;
             const node =
-                await opts.imaState.joNodes.callStatic.nodes( nodeId, { from: addressFrom } );
+                await opts.imaState.joNodes.callStatic.nodes( nodeId );
             const nodeDict = {
                 "id": nodeId,
                 "name": node[0],
                 "ip": owaspUtils.ipFromHex( node[1] ),
                 "basePort": node[3],
-                "domain":
-                    await opts.imaState.joNodes.callStatic.getNodeDomainName(
-                        nodeId, { from: addressFrom } ),
+                "domain": await opts.imaState.joNodes.callStatic.getNodeDomainName( nodeId ),
                 "isMaintenance":
-                    await opts.imaState.joNodes.callStatic.isNodeInMaintenance(
-                        nodeId, { from: addressFrom } )
+                    await opts.imaState.joNodes.callStatic.isNodeInMaintenance( nodeId )
             };
             if( opts && opts.bStopNeeded )
                 return;
             const arrFetchedSChainIds =
                 await opts.imaState.joSChainsInternal.callStatic.getSchainHashesForNode(
-                    nodeId, { from: addressFrom } );
+                    nodeId );
             nodeDict.basePortOfSChain =
                 getSChainBasePortOnNode(
                     computedSChainId, arrFetchedSChainIds, nodeDict.basePort );
@@ -320,13 +318,13 @@ export async function loadSChainParts( joSChain, addressFrom, opts ) {
     joSChain.data.computed.nodes = nodes;
 }
 
-export async function getSChainsCount( addressFrom, opts ) {
+export async function getSChainsCount( opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot get S-Chains count, no imaState is provided" );
-    const cntSChains =
-        await opts.imaState.joSChainsInternal.callStatic.numberOfSchains(
-            { from: addressFrom } );
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot get S-Chains count, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
+    const cntSChains = await opts.imaState.joSChainsInternal.callStatic.numberOfSchains();
     return cntSChains;
 }
 
@@ -351,20 +349,21 @@ function process_sc_data( rawData ) {
     return joData;
 }
 
-export async function loadSChain( addressFrom, idxSChain, hash, joData, cntSChains, opts ) {
+export async function loadSChain( idxSChain, hash, joData, cntSChains, opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chain description in observer, no imaState is provided" );
+    if( ! opts.imaState ) {
+        throw new Error(
+            "Cannot load S-Chain description in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             opts.details.write( cc.debug( "Loading S-Chain " ) + cc.notice( "#" ) +
                 cc.info( idxSChain + 1 ) + cc.debug( " of " ) + cc.info( cntSChains ) +
-                cc.debug( "..." ) + "\n" );
+                cc.debug( " in " ) + threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
         }
     }
-    hash = hash ||
-        await opts.imaState.joSChainsInternal.callStatic.schainsAtSystem(
-            idxSChain, { from: addressFrom } );
+    hash = hash || await opts.imaState.joSChainsInternal.callStatic.schainsAtSystem( idxSChain );
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace )
             opts.details.write( cc.debug( "    Hash " ) + cc.attention( hash ) + "\n" );
@@ -372,17 +371,16 @@ export async function loadSChain( addressFrom, idxSChain, hash, joData, cntSChai
     if( opts && opts.bStopNeeded )
         return null;
     joData = joData ||
-        process_sc_data( await opts.imaState.joSChainsInternal.callStatic.schains(
-            hash, { from: addressFrom } ) );
+        process_sc_data( await opts.imaState.joSChainsInternal.callStatic.schains( hash ) );
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace )
             opts.details.write( cc.debug( "    Data of chain is " ) + cc.j( joData ) + "\n" );
     }
     const joSChain = { "data": joData };
-    removeSChainDescDataNumKeys( joSChain.data, addressFrom );
+    removeSChainDescDataNumKeys( joSChain.data );
     if( opts && opts.bStopNeeded )
         return null;
-    await loadSChainParts( joSChain, addressFrom, opts );
+    await loadSChainParts( joSChain, opts );
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             opts.details.write( cc.debug( "    Desc " ) + cc.j( joSChain.data ) + "\n" );
@@ -393,24 +391,28 @@ export async function loadSChain( addressFrom, idxSChain, hash, joData, cntSChai
     return joSChain;
 }
 
-export async function loadSChains( addressFrom, opts ) {
+export async function loadSChains( opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chains parts in observer, no imaState is provided" );
+    if( ! opts.imaState ) {
+        throw new Error(
+            "Cannot load S-Chains parts in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     let isEMC = false;
     if( opts.imaState.isEnabledMultiCall )
         isEMC = await isMulticallAvailable( opts.imaState.chainProperties.mn );
     if( isEMC )
-        return await loadSChainsWithEMC( addressFrom, opts );
-    return await loadSChainsOptimal( addressFrom, opts );
+        return await loadSChainsWithEMC( opts );
+    return await loadSChainsOptimal( opts );
 }
 
-export async function loadSChainsWithEMC( addressFrom, opts ) {
-    const cntSChains = await getSChainsCount( addressFrom, opts );
+export async function loadSChainsWithEMC( opts ) {
+    const cntSChains = await getSChainsCount( opts );
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             opts.details.write( cc.debug( "Have " ) + cc.info( cntSChains ) +
-                cc.debug( " S-Chain(s) to load..." ) + "\n" );
+                cc.debug( " S-Chain(s) to load in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
         }
     }
     const multicall = new EMC.Multicall( {
@@ -553,7 +555,7 @@ export async function loadSChainsWithEMC( addressFrom, opts ) {
         const hash = arrSChainHashes[idxSChain];
         const joData = arrSChainDataRecords[idxSChain];
         const joSChain = await loadSChain( // with hash + joData
-            addressFrom, idxSChain, hash, joData, cntSChains, opts );
+            idxSChain, hash, joData, cntSChains, opts );
         if( ! joSChain )
             break;
         arrSChains.push( joSChain );
@@ -567,23 +569,25 @@ export async function loadSChainsWithEMC( addressFrom, opts ) {
     return arrSChains;
 }
 
-export async function loadSChainsOptimal( addressFrom, opts ) {
+export async function loadSChainsOptimal( opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chains in observer, no imaState is provided" );
-    const cntSChains = await getSChainsCount( addressFrom, opts );
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot load S-Chains in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
+    const cntSChains = await getSChainsCount( opts );
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             opts.details.write( cc.debug( "Have " ) + cc.info( cntSChains ) +
-                cc.debug( " S-Chain(s) to load..." ) + "\n" );
+                cc.debug( " S-Chain(s) to load in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
         }
     }
     const arrSChains = [];
     for( let idxSChain = 0; idxSChain < cntSChains; ++ idxSChain ) {
         if( opts && opts.bStopNeeded )
             break;
-        const joSChain = await loadSChain(
-            addressFrom, idxSChain, null, null, cntSChains, opts );
+        const joSChain = await loadSChain( idxSChain, null, null, cntSChains, opts );
         if( ! joSChain )
             break;
         arrSChains.push( joSChain );
@@ -591,22 +595,27 @@ export async function loadSChainsOptimal( addressFrom, opts ) {
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
             opts.details.write( cc.success( "All " ) + cc.info( cntSChains ) +
-                cc.debug( " S-Chain(s) loaded:" ) + cc.j( arrSChains ) + "\n" );
+                cc.debug( " S-Chain(s) loaded in " ) +
+                threadInfo.threadDescription() + cc.success( ": " ) +
+                cc.j( arrSChains ) + "\n" );
         }
     }
     return arrSChains;
 }
 
 export async function loadCachedSChainsSimplified( addressFrom, opts ) {
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chains in observer, no imaState is provided" );
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot load S-Chains in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     if( opts && opts.details ) {
-        if( log.verboseGet() >= log.verboseReversed().trace )
-            opts.details.write( cc.debug( "Will request all S-Chain(s) hashes..." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().trace ) {
+            opts.details.write( cc.debug( "Will request all S-Chain(s) hashes in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
     }
     const arrSChainHashes =
-        await opts.imaState.joSChainsInternal.callStatic.getSchains(
-            { from: addressFrom } );
+        await opts.imaState.joSChainsInternal.callStatic.getSchains();
     const cntSChains = arrSChainHashes.length;
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
@@ -620,8 +629,7 @@ export async function loadCachedSChainsSimplified( addressFrom, opts ) {
             break;
         const strSChainHash = arrSChainHashes[idxSChain];
         const strSChainName =
-            await opts.imaState.joSChainsInternal.callStatic.getSchainName(
-                strSChainHash, { from: addressFrom } );
+            await opts.imaState.joSChainsInternal.callStatic.getSchainName( strSChainHash );
         if( opts && opts.details ) {
             if( log.verboseGet() >= log.verboseReversed().trace ) {
                 opts.details.write( cc.debug( "S-Chain " ) + cc.notice( idxSChain ) +
@@ -632,8 +640,7 @@ export async function loadCachedSChainsSimplified( addressFrom, opts ) {
         }
         if( opts && opts.bStopNeeded )
             break;
-        const joSChain = await loadSChain(
-            addressFrom, idxSChain, strSChainHash, null, cntSChains, opts );
+        const joSChain = await loadSChain( idxSChain, strSChainHash, null, cntSChains, opts );
         if( ! joSChain )
             break;
         arrSChains.push( joSChain );
@@ -641,20 +648,18 @@ export async function loadCachedSChainsSimplified( addressFrom, opts ) {
     return arrSChains;
 }
 
-export async function loadSChainsConnectedOnly(
-    strChainNameConnectedTo,
-    addressFrom,
-    opts
-) {
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chains in observer, no imaState is provided" );
+export async function loadSChainsConnectedOnly( strChainNameConnectedTo, opts ) {
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot load S-Chains in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     if( opts && opts.details ) {
-        if( log.verboseGet() >= log.verboseReversed().trace )
-            opts.details.write( cc.debug( "Will request all S-Chain(s) hashes..." ) + "\n" );
+        if( log.verboseGet() >= log.verboseReversed().trace ) {
+            opts.details.write( cc.debug( "Will request all S-Chain(s) hashes in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
     }
-    const arrSChainHashes =
-        await opts.imaState.joSChainsInternal.callStatic.getSchains(
-            { from: addressFrom } );
+    const arrSChainHashes = await opts.imaState.joSChainsInternal.callStatic.getSchains();
     const cntSChains = arrSChainHashes.length;
     if( opts && opts.details ) {
         if( log.verboseGet() >= log.verboseReversed().trace ) {
@@ -675,8 +680,7 @@ export async function loadSChainsConnectedOnly(
                 break;
             const strSChainHash = arrSChainHashes[idxSChain];
             const strSChainName =
-                await opts.imaState.joSChainsInternal.callStatic.getSchainName(
-                    strSChainHash, { from: addressFrom } );
+                await opts.imaState.joSChainsInternal.callStatic.getSchainName( strSChainHash );
             if( opts && opts.details ) {
                 if( log.verboseGet() >= log.verboseReversed().trace ) {
                     opts.details.write( cc.debug( "S-Chain " ) + cc.notice( idxSChain ) +
@@ -706,8 +710,7 @@ export async function loadSChainsConnectedOnly(
                 }
             }
             const isConnected =
-                await joMessageProxySChain.callStatic.isConnectedChain(
-                    strSChainName, { from: addressFrom } );
+                await joMessageProxySChain.callStatic.isConnectedChain( strSChainName );
             if( opts && opts.details ) {
                 if( log.verboseGet() >= log.verboseReversed().trace ) {
                     opts.details.write( cc.debug( "Got S-Chain " ) + cc.info( strSChainName ) +
@@ -716,8 +719,7 @@ export async function loadSChainsConnectedOnly(
             }
             if( ! isConnected )
                 continue;
-            const joSChain = await loadSChain(
-                addressFrom, idxSChain, strSChainHash, null, cntSChains, opts );
+            const joSChain = await loadSChain( idxSChain, strSChainHash, null, cntSChains, opts );
             if( ! joSChain )
                 break;
             joSChain.isConnected = true;
@@ -735,12 +737,12 @@ export async function loadSChainsConnectedOnly(
     return arrSChains;
 }
 
-export async function checkConnectedSChains(
-    strChainNameConnectedTo, arrSChains, addressFrom, opts
-) {
+export async function checkConnectedSChains( strChainNameConnectedTo, arrSChains, opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
-    if( ! opts.imaState )
-        throw new Error( "Cannot load S-Chains in observer, no imaState is provided" );
+    if( ! opts.imaState ) {
+        throw new Error( "Cannot load S-Chains in observer, no imaState is provided in " +
+            threadInfo.threadDescription( false ) );
+    }
     const cntSChains = arrSChains.length;
     for( let idxSChain = 0; idxSChain < cntSChains; ++ idxSChain ) {
         if( opts && opts.bStopNeeded )
@@ -767,8 +769,7 @@ export async function checkConnectedSChains(
                     ethersProvider
                 );
             joSChain.isConnected =
-                await joMessageProxySChain.callStatic.isConnectedChain(
-                    strChainNameConnectedTo, { from: addressFrom } );
+                await joMessageProxySChain.callStatic.isConnectedChain( strChainNameConnectedTo );
             if( opts && opts.details ) {
                 if( log.verboseGet() >= log.verboseReversed().trace )
                     opts.details.write( cc.debug( "Got " ) + cc.yn( joSChain.isConnected ) + "\n" );
@@ -902,12 +903,14 @@ export function mergeSChainsArrayFromTo( arrSrc, arrDst, arrNew, arrOld, opts )
 }
 
 let gArrSChainsCached = [];
+const gArrCacheHistory = [];
+let nMaxSizeOfArrCacheHistory = 20;
 
-export async function cacheSChains( strChainNameConnectedTo, addressFrom, opts ) {
+export async function cacheSChains( strChainNameConnectedTo, opts ) {
     owaspUtils.ensureObserverOptionsInitialized( opts );
     let strError = null;
     try {
-        const arrSChains = await loadSChains( addressFrom, opts );
+        const arrSChains = await loadSChains( opts );
         if( strChainNameConnectedTo &&
             ( typeof strChainNameConnectedTo == "string" ) &&
             strChainNameConnectedTo.length > 0
@@ -915,7 +918,6 @@ export async function cacheSChains( strChainNameConnectedTo, addressFrom, opts )
             await checkConnectedSChains(
                 strChainNameConnectedTo,
                 arrSChains,
-                addressFrom,
                 opts
             );
             gArrSChainsCached = await filterSChainsMarkedAsConnected(
@@ -927,16 +929,36 @@ export async function cacheSChains( strChainNameConnectedTo, addressFrom, opts )
         if( opts && opts.details ) {
             if( log.verboseGet() >= log.verboseReversed().trace ) {
                 opts.details.write( cc.debug( "Connected " ) + cc.attention( "S-Chains" ) +
-                    cc.debug( " cache was updated in this thread: " ) +
-                    cc.j( gArrSChainsCached ) + "\n" );
+                    cc.debug( " cache was updated in " ) + threadInfo.threadDescription() +
+                    cc.debug( ": " ) + cc.j( gArrSChainsCached ) + "\n" );
+            }
+        }
+        if( opts && opts.details ) {
+            if( log.verboseGet() >= log.verboseReversed().trace ) {
+                opts.details.write(
+                    cc.debug( "Will dispatch inThread-arrSChainsCached event in " ) +
+                    threadInfo.threadDescription() + "\n" );
+            }
+        }
+        events.dispatchEvent(
+            new UniversalDispatcherEvent(
+                "inThread-arrSChainsCached",
+                { "detail": { "arrSChainsCached": arrSChains } } ) );
+        if( opts && opts.details ) {
+            if( log.verboseGet() >= log.verboseReversed().trace ) {
+                opts.details.write(
+                    cc.debug( "Did dispatched inThread-arrSChainsCached event in " ) +
+                    threadInfo.threadDescription() + "\n" );
             }
         }
         if( opts.fnCacheChanged )
             opts.fnCacheChanged( gArrSChainsCached, null ); // null - no error
     } catch ( err ) {
         strError = owaspUtils.extractErrorMessage( err );
-        if( ! strError )
-            strError = "unknown exception during S-Chains download";
+        if( ! strError ) {
+            strError = "unknown exception during S-Chains download in " +
+                threadInfo.threadDescription( false );
+        }
         if( opts.fnCacheChanged )
             opts.fnCacheChanged( gArrSChainsCached, strError );
         if( opts && opts.details ) {
@@ -955,18 +977,57 @@ export function getLastCachedSChains() {
 }
 
 export function setLastCachedSChains( arrSChainsCached ) {
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        log.write( cc.debug( "Will set arrSChainsCached in " ) +
+            threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        log.write( cc.debug( "Value of arrSChainsCached in " ) +
+            threadInfo.threadDescription() + cc.debug( " is: " ) +
+            cc.j( arrSChainsCached ) + "\n" );
+    }
     if( arrSChainsCached && typeof arrSChainsCached == "object" ) {
         gArrSChainsCached = JSON.parse( JSON.stringify( arrSChainsCached ) );
+        gArrCacheHistory.push( {
+            "ts": "" + log.generateTimestampString( null, false ),
+            "arrSChainsCached": JSON.parse( JSON.stringify( arrSChainsCached ) )
+        } );
+        const nMaxSize = getLastCachedHistoryMaxSize();
+        while( gArrCacheHistory.length > nMaxSize )
+            gArrCacheHistory.shift();
+        if( log.verboseGet() >= log.verboseReversed().debug ) {
+            log.write( cc.debug( "Will dispatch arrSChainsCached event in " ) +
+                threadInfo.threadDescription() + cc.debug( "..." ) + "\n" );
+        }
         events.dispatchEvent(
             new UniversalDispatcherEvent(
                 "chainsCacheChanged",
                 { "detail": { "arrSChainsCached": getLastCachedSChains() } } ) );
+    } else {
+        if( log.verboseGet() >= log.verboseReversed().error ) {
+            log.write( cc.fatal( "CRITICAL ERROR:" ) +
+                cc.error( " Cannot dispatch arrSChainsCached event with bad object " ) +
+                cc.j( arrSChainsCached ) + cc.error( " in " ) +
+                threadInfo.threadDescription() + "\n" );
+        }
     }
 }
 
-const sleepImpl = ( milliseconds ) => {
-    return new Promise( resolve => setTimeout( resolve, milliseconds ) );
-};
+export function getLastCachedHistory() {
+    return gArrCacheHistory;
+}
+
+export function getLastCachedHistoryMaxSize() {
+    return 0 + nMaxSizeOfArrCacheHistory;
+}
+export function setLastCachedHistoryMaxSize( m ) {
+    nMaxSizeOfArrCacheHistory = 0 + n;
+    if( nMaxSizeOfArrCacheHistory < 0 )
+        nMaxSizeOfArrCacheHistory = 0;
+}
+
+export async function refreshNowSNB( opts ) {
+    const strChainNameConnectedTo = opts.imaState.chainProperties.sc.strChainName;
+    await cacheSChains( strChainNameConnectedTo, opts );
+}
 
 let gWorker = null;
 let gClient = null;
@@ -977,7 +1038,7 @@ export async function ensureHaveWorker( opts ) {
         return gWorker;
     const url = "skale_observer_worker_server";
     gWorker =
-        new Worker(
+        new threadInfo.Worker(
             path.join( __dirname, "observerWorker.mjs" ),
             { "type": "module" }
         );
@@ -992,15 +1053,16 @@ export async function ensureHaveWorker( opts ) {
         case "periodicCachingDoNow":
             if( log.verboseGet() >= log.verboseReversed().debug ) {
                 opts.details.write(
-                    cc.debug( "Parallel periodic SNB caching result did arrived to main thread" ) +
-                    "\n" );
+                    cc.debug( "Parallel periodic SNB caching result did arrived to " ) +
+                    threadInfo.threadDescription() + "\n" );
             }
             setLastCachedSChains( joMessage.message );
             gFlagHaveParallelResult = true;
             if( opts && opts.details ) {
                 if( log.verboseGet() >= log.verboseReversed().trace ) {
                     opts.details.write( cc.debug( "Connected " ) + cc.attention( "S-Chains" ) +
-                        cc.debug( " cache was updated using data arrived from SNB worker: " ) +
+                        cc.debug( " cache was updated using data arrived from SNB worker in " ) +
+                        threadInfo.threadDescription() + cc.debug( ": " ) +
                         cc.j( gArrSChainsCached ) + "\n" );
                 }
             }
@@ -1010,7 +1072,7 @@ export async function ensureHaveWorker( opts ) {
             break;
         } // switch ( joMessage.method )
     } );
-    await sleepImpl( 1000 );
+    await threadInfo.sleep( 1000 );
     const jo = {
         "method": "init",
         "message": {
@@ -1107,12 +1169,12 @@ export async function ensureHaveWorker( opts ) {
     gClient.send( jo );
 }
 
-async function inThreadPeriodicCachingStart( strChainNameConnectedTo, addressFrom, opts ) {
+async function inThreadPeriodicCachingStart( strChainNameConnectedTo, opts ) {
     if( gIntervalPeriodicCaching != null )
         return;
     try {
         const fnDoCachingNow = async function() {
-            await cacheSChains( strChainNameConnectedTo, addressFrom, opts );
+            await cacheSChains( strChainNameConnectedTo, opts );
         };
         gIntervalPeriodicCaching =
             setInterval(
@@ -1122,7 +1184,8 @@ async function inThreadPeriodicCachingStart( strChainNameConnectedTo, addressFro
         return true;
     } catch ( err ) {
         if( log.verboseGet() >= log.verboseReversed().error ) {
-            log.write( cc.error( "Failed to start in-thread periodic SNB refresh, error is: " ) +
+            log.write( cc.error( "Failed to start in-thread periodic SNB refresh in " ) +
+                threadInfo.threadDescription() + cc.error( ", error is: " ) +
                 cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
         }
@@ -1130,7 +1193,7 @@ async function inThreadPeriodicCachingStart( strChainNameConnectedTo, addressFro
     return false;
 }
 
-async function parallelPeriodicCachingStart( strChainNameConnectedTo, addressFrom, opts ) {
+async function parallelPeriodicCachingStart( strChainNameConnectedTo, opts ) {
     gFlagHaveParallelResult = false;
     try {
         const nSecondsToWaitParallel = ( opts.secondsToWaitForSkaleNetworkDiscovered > 0 )
@@ -1144,19 +1207,20 @@ async function parallelPeriodicCachingStart( strChainNameConnectedTo, addressFro
             if( gFlagHaveParallelResult )
                 return;
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( cc.error( "Failed to start parallel periodic SNB refresh, error is: " ) +
+                log.write( cc.error( "Failed to start parallel periodic SNB refresh in " ) +
+                    threadInfo.threadDescription() + cc.error( ", error is: " ) +
                     cc.warning( "timeout of " ) + cc.info( nSecondsToWaitParallel ) +
                     cc.warning( " reached, will restart periodic SNB refresh " +
                         "in non-parallel mode" ) + "\n" );
             }
             periodicCachingStop();
-            inThreadPeriodicCachingStart( strChainNameConnectedTo, addressFrom, opts );
+            inThreadPeriodicCachingStart( strChainNameConnectedTo, opts );
         }, nSecondsToWaitParallel * 1000 );
         owaspUtils.ensureObserverOptionsInitialized( opts );
         await ensureHaveWorker( opts );
         if( log.verboseGet() >= log.verboseReversed().debug ) {
-            log.write( cc.debug( "Informing worker thread to start periodic SNB refresh..." ) +
-                "\n" );
+            log.write( threadInfo.threadDescription() +
+                cc.debug( " will inform worker thread to start periodic SNB refresh..." ) + "\n" );
         }
         const jo = {
             "method": "periodicCachingStart",
@@ -1165,19 +1229,19 @@ async function parallelPeriodicCachingStart( strChainNameConnectedTo, addressFro
                     parseInt( opts.secondsToReDiscoverSkaleNetwork ),
                 "secondsToWaitForSkaleNetworkDiscovered":
                     parseInt( opts.secondsToWaitForSkaleNetworkDiscovered ),
-                "strChainNameConnectedTo": strChainNameConnectedTo,
-                "addressFrom": addressFrom
+                "strChainNameConnectedTo": strChainNameConnectedTo
             }
         };
         gClient.send( jo );
         if( log.verboseGet() >= log.verboseReversed().debug ) {
-            log.write( cc.debug( "Did informed worker thread to start periodic SNB refresh" ) +
-                "\n" );
+            log.write( threadInfo.threadDescription() +
+                cc.debug( " did informed worker thread to start periodic SNB refresh" ) + "\n" );
         }
         return true;
     } catch ( err ) {
         if( log.verboseGet() >= log.verboseReversed().error ) {
-            log.write( cc.error( "Failed to start parallel periodic SNB refresh, error is: " ) +
+            log.write( cc.error( "Failed to start parallel periodic SNB refresh in " ) +
+                threadInfo.threadDescription() + cc.error( ", error is: " ) +
                 cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                 cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
         }
@@ -1185,7 +1249,7 @@ async function parallelPeriodicCachingStart( strChainNameConnectedTo, addressFro
     return false;
 }
 
-export async function periodicCachingStart( strChainNameConnectedTo, addressFrom, opts ) {
+export async function periodicCachingStart( strChainNameConnectedTo, opts ) {
     gFlagHaveParallelResult = false;
     const bParallelModeRefreshSNB =
         ( opts && "bParallelModeRefreshSNB" in opts &&
@@ -1195,11 +1259,11 @@ export async function periodicCachingStart( strChainNameConnectedTo, addressFrom
     let wasStarted = false;
     if( bParallelModeRefreshSNB ) {
         wasStarted = await
-        parallelPeriodicCachingStart( strChainNameConnectedTo, addressFrom, opts );
+        parallelPeriodicCachingStart( strChainNameConnectedTo, opts );
     }
     if( wasStarted )
         return;
-    await inThreadPeriodicCachingStart( strChainNameConnectedTo, addressFrom, opts );
+    await inThreadPeriodicCachingStart( strChainNameConnectedTo, opts );
 }
 
 export async function periodicCachingStop() {
@@ -1214,12 +1278,13 @@ export async function periodicCachingStop() {
             gClient = null;
             gWorker = null;
             refClient.send( jo );
-            await sleepImpl( 100 );
+            await threadInfo.sleep( 100 );
             refClient.dispose();
             await refWorker.terminate();
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( cc.error( "Failed to stop parallel periodic SNB refresh, error is: " ) +
+                log.write( cc.error( "Failed to stop parallel periodic SNB refresh in " ) +
+                    threadInfo.threadDescription() + cc.error( ", error is: " ) +
                     cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
@@ -1231,7 +1296,8 @@ export async function periodicCachingStop() {
             gIntervalPeriodicCaching = null;
         } catch ( err ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
-                log.write( cc.error( "Failed to stop in-thread periodic SNB refresh, error is: " ) +
+                log.write( cc.error( "Failed to stop in-thread periodic SNB refresh in " ) +
+                    threadInfo.threadDescription() + cc.error( ", error is: " ) +
                     cc.warning( owaspUtils.extractErrorMessage( err ) ) +
                     cc.error( ", stack is: " ) + "\n" + cc.stack( err.stack ) + "\n" );
             }
diff --git a/npms/skale-observer/observerWorker.mjs b/npms/skale-observer/observerWorker.mjs
index 488fabec8..005884ef0 100644
--- a/npms/skale-observer/observerWorker.mjs
+++ b/npms/skale-observer/observerWorker.mjs
@@ -32,6 +32,7 @@ import * as cc from "../skale-cc/cc.mjs";
 import * as owaspUtils from "../skale-owasp/owaspUtils.mjs";
 import * as skaleObserver from "./observer.mjs";
 import * as log from "../skale-log/log.mjs";
+import * as threadInfo from "../../agent/threadInfo.mjs";
 
 const gURL = "skale_observer_worker_server";
 
@@ -102,7 +103,8 @@ class ObserverServer extends SocketServer {
                     self.log( cc.warning( "WARNING:" ) + cc.warning( " No " ) +
                         cc.note( "Main-net" ) +
                         cc.warning( " URL specified in command line arguments" ) +
-                        cc.debug( "(needed for particular operations only)" ) + "\n" );
+                        cc.debug( "(needed for particular operations only) in " ) +
+                        threadInfo.threadDescription() + "\n" );
                 }
             }
 
@@ -118,7 +120,8 @@ class ObserverServer extends SocketServer {
                     self.log( cc.warning( "WARNING:" ) + cc.warning( " No " ) +
                         cc.note( "Main-net" ) +
                         cc.warning( " URL specified in command line arguments" ) +
-                        cc.debug( "(needed for particular operations only)" ) + "\n" );
+                        cc.debug( "(needed for particular operations only) in " ) +
+                        threadInfo.threadDescription() + "\n" );
                 }
             }
             self.opts.imaState.joNodes =
@@ -147,7 +150,8 @@ class ObserverServer extends SocketServer {
                     self.opts.imaState.chainProperties.sc.ethersProvider
                 );
             if( log.verboseGet() >= log.verboseReversed().information ) {
-                self.log( cc.debug( "Full init compete for in-worker SNB server" ) + " " +
+                self.log( cc.debug( "Full init compete for in-worker SNB server in " ) +
+                    threadInfo.threadDescription() + " " +
                     cc.notice( gURL ) + "\n" );
             }
             return joAnswer;
@@ -156,14 +160,14 @@ class ObserverServer extends SocketServer {
             function( joMessage, joAnswer, eventData, socket ) {
                 if( log.verboseGet() >= log.verboseReversed().debug ) {
                     self.opts.details.write(
-                        cc.debug( "Worker thread will to start periodic SNB refresh..." ) +
+                        threadInfo.threadDescription() +
+                        cc.debug( " will to start periodic SNB refresh ..." ) +
                         "\n" );
                 }
                 self.periodicCachingStart(
                     socket,
                     joMessage.message.secondsToReDiscoverSkaleNetwork,
-                    joMessage.message.strChainNameConnectedTo,
-                    joMessage.message.addressFrom
+                    joMessage.message.strChainNameConnectedTo
                 );
                 joAnswer.message = {
                     "method": "" + joMessage.method,
@@ -181,7 +185,8 @@ class ObserverServer extends SocketServer {
             return joAnswer;
         };
         if( log.verboseGet() >= log.verboseReversed().information ) {
-            self.log( cc.debug( "Initialized in-worker SNB server" ) + " " +
+            self.log( cc.debug( "Initialized in-worker SNB server in " ) +
+                threadInfo.threadDescription() + cc.debug( " is " ) +
                 cc.notice( gURL ) + "\n" );
         }
     }
@@ -195,7 +200,7 @@ class ObserverServer extends SocketServer {
         super.dispose();
     }
     async periodicCachingDoNow(
-        socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo, addressFrom
+        socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo
     ) {
         const self = this;
         if( self.bIsPeriodicCachingStepInProgress )
@@ -205,22 +210,23 @@ class ObserverServer extends SocketServer {
         for( let idxAttempt = 0; idxAttempt < 10; ++ idxAttempt ) {
             try {
                 if( log.verboseGet() >= log.verboseReversed().debug ) {
-                    self.opts.details.write(
-                        cc.debug( "Worker thread will invoke S-Chains caching..." ) +
+                    self.opts.details.write( threadInfo.threadDescription() +
+                        cc.debug( " thread will invoke S-Chains caching..." ) +
                         "\n" );
                 }
                 strError =
                     await skaleObserver.cacheSChains(
                         strChainNameConnectedTo,
-                        addressFrom,
                         self.opts
                     );
                 if( ! strError )
                     break;
             } catch ( err ) {
                 strError = owaspUtils.extractErrorMessage( err );
-                if( ! strError )
-                    strError = "runtime error without description";
+                if( ! strError ) {
+                    strError = "runtime error without description in " +
+                        threadInfo.threadDescription( false );
+                }
             }
             await sleep( 5 * 1000 );
         }
@@ -228,12 +234,14 @@ class ObserverServer extends SocketServer {
         if( strError ) {
             if( log.verboseGet() >= log.verboseReversed().error ) {
                 self.log( cc.error( "Parallel periodic SNB caching came across with error: " ) +
-                    cc.warning( strError ) + "\n" );
+                    cc.warning( strError ) + cc.error( " in " ) + threadInfo.threadDescription() +
+                    "\n" );
             }
             return strError;
         }
         if( log.verboseGet() >= log.verboseReversed().debug ) {
-            self.log( cc.debug( "Parallel periodic SNB caching will notify main thread now" ) +
+            self.log( cc.debug( "Parallel periodic SNB caching in " ) +
+                threadInfo.threadDescription() + cc.debug( " will notify main thread now" ) +
                 "\n" );
         }
         const arrSChains = skaleObserver.getLastCachedSChains();
@@ -245,14 +253,13 @@ class ObserverServer extends SocketServer {
         const isFlush = true;
         socket.send( jo, isFlush );
         if( log.verboseGet() >= log.verboseReversed().debug ) {
-            self.log( cc.debug( "Parallel periodic SNB caching did notified main thread now" ) +
+            self.log( cc.debug( "Parallel periodic SNB caching in " ) +
+                threadInfo.threadDescription() + cc.debug( " did notified main thread now" ) +
                 "\n" );
         }
         return null;
     }
-    async periodicCachingStart(
-        socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo, addressFrom
-    ) {
+    async periodicCachingStart( socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo ) {
         const self = this;
         await self.periodicCachingStop();
         if( secondsToReDiscoverSkaleNetwork <= 0 )
@@ -261,15 +268,16 @@ class ObserverServer extends SocketServer {
             try {
                 if( log.verboseGet() >= log.verboseReversed().debug ) {
                     self.opts.details.write(
-                        cc.debug( "SKALE Observer will do immediate periodic SNB refresh..." ) +
+                        cc.debug( "SKALE Observer in " ) + threadInfo.threadDescription() +
+                        cc.debug( " will do immediate periodic SNB refresh..." ) +
                         "\n" );
                 }
                 await self.periodicCachingDoNow(
-                    socket, secondsToReDiscoverSkaleNetwork,
-                    strChainNameConnectedTo, addressFrom );
+                    socket, secondsToReDiscoverSkaleNetwork, strChainNameConnectedTo );
             } catch ( err ) {
                 if( log.verboseGet() >= log.verboseReversed().error ) {
-                    self.log( cc.error( "Periodic SNB caching(async) error: " ) +
+                    self.log( cc.error( "Periodic SNB caching(async) error in " ) +
+                        threadInfo.threadDescription() + cc.debug( ": " ) +
                         cc.warning( strError ) + "\n" );
                 }
             }
@@ -282,13 +290,15 @@ class ObserverServer extends SocketServer {
                     .then( () => {
                     } ).catch( ( err ) => {
                         if( log.verboseGet() >= log.verboseReversed().error ) {
-                            self.log( cc.error( "Periodic SNB caching(sync-delayed) error: " ) +
+                            self.log( cc.error( "Periodic SNB caching(sync-delayed) in " ) +
+                                threadInfo.threadDescription() + cc.error( " error: " ) +
                                 cc.warning( owaspUtils.extractErrorMessage( err ) ) + "\n" );
                         }
                     } );
             } catch ( err ) {
                 if( log.verboseGet() >= log.verboseReversed().error ) {
-                    self.log( cc.error( "Periodic SNB caching(sync) error: " ) +
+                    self.log( cc.error( "Periodic SNB caching(sync) in " ) +
+                        threadInfo.threadDescription() + cc.error( " error: " ) +
                         cc.warning( owaspUtils.extractErrorMessage( err ) ) + "\n" );
                 }
             }
@@ -296,7 +306,8 @@ class ObserverServer extends SocketServer {
         await fnPeriodicCaching();
         if( log.verboseGet() >= log.verboseReversed().debug ) {
             self.opts.details.write(
-                cc.debug( "SKALE Observer will to start periodic SNB refresh..." ) +
+                cc.debug( "SKALE Observer in " ) + threadInfo.threadDescription() +
+                cc.debug( " will to start periodic SNB refresh..." ) +
                 "\n" );
         }
         self.intervalPeriodicSchainsCaching = setInterval(
@@ -319,6 +330,9 @@ const acceptor = new networkLayer.InWorkerSocketServerAcceptor( gURL, doSendMess
 const server = new ObserverServer( acceptor );
 server.on( "dispose", function() {
     const self = server;
-    if( log.verboseGet() >= log.verboseReversed().debug )
-        self.log( cc.debug( "Disposed in-worker SNB server" ) + " " + cc.notice( gURL ) + "\n" );
+    if( log.verboseGet() >= log.verboseReversed().debug ) {
+        self.log( cc.debug( "Disposed in-worker in " ) +
+            threadInfo.threadDescription() + cc.debug( " SNB server" ) + " " +
+            cc.notice( gURL ) + "\n" );
+    }
 } );
diff --git a/npms/skale-owasp/owaspUtils.mjs b/npms/skale-owasp/owaspUtils.mjs
index 7531f04ca..9e1f074ba 100644
--- a/npms/skale-owasp/owaspUtils.mjs
+++ b/npms/skale-owasp/owaspUtils.mjs
@@ -811,3 +811,15 @@ export function toBN( x ) {
 export function isNumeric( s ) {
     return /^\d+$/.test( s );
 }
+
+export function toHexStringSafe( val ) {
+    if( ! val )
+        return "0x0";
+    if( "toHexString" in val && typeof val.toHexString == "function" )
+        return val.toHexString();
+    if( typeof val == "number" || typeof val == "bigint" )
+        return ensureStartsWith0x( val.toString( 16 ) );
+    if( "toString" in val && typeof val.toString == "function" )
+        return val.toString();
+    return "" + val;
+}


Test patch:
diff --git a/proxy/predeployed/test/test.sh b/proxy/predeployed/test/test.sh
index 1103ed642..8c032fe3c 100755
--- a/proxy/predeployed/test/test.sh
+++ b/proxy/predeployed/test/test.sh
@@ -6,19 +6,8 @@ cd "$(dirname "$0")"
 
 BLOCKCHAIN_DIR="/tmp/blockchain/"
 
-# run geth to initialize ancient database
-# TODO: remove when geth fixes --dev mode
-echo "Run geth to initialize ancient database"
-geth --datadir "$BLOCKCHAIN_DIR" &
-GETH_PID=$!
-sleep 1
-echo "Geth PID=$GETH_PID"
-kill -SIGINT $GETH_PID
-echo "Stop geth"
-wait $GETH_PID
-
 echo "Run geth in dev mode"
-geth --datadir "$BLOCKCHAIN_DIR" --dev --http &
+geth --datadir "$BLOCKCHAIN_DIR" --http &
 GETH_PID=$!
 sleep 3
 
diff --git a/test/agent-test.mjs b/test/agent-test.mjs
index e4e70a1dd..08f0bb0d7 100644
--- a/test/agent-test.mjs
+++ b/test/agent-test.mjs
@@ -265,7 +265,7 @@ const imaState = {
 
     "optsS2S": {
         "isEnabled": false,
-        "bParallelModeRefreshSNB": false,
+        "bParallelModeRefreshSNB": true,
         "secondsToReDiscoverSkaleNetwork": 10 * 60,
         "secondsToWaitForSkaleNetworkDiscovered": 2 * 60
     },