From 3df4ce8dd174c80cbc42848b1bccdb0f29913aef Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Mon, 2 Jun 2025 19:11:13 +0100 Subject: [PATCH 01/18] Provide syncer session call back handlers that can be intercepted --- execution_chain/sync/beacon.nim | 76 ++++++++++++++--- execution_chain/sync/beacon/beacon_desc.nim | 6 ++ .../beacon/worker/blocks/blocks_blocks.nim | 19 ++++- .../beacon/worker/blocks/blocks_fetch.nim | 25 +++++- .../beacon/worker/blocks/blocks_import.nim | 7 +- .../beacon/worker/headers/headers_fetch.nim | 23 +++++- .../sync/beacon/worker/start_stop.nim | 6 +- execution_chain/sync/beacon/worker/update.nim | 39 ++++----- .../sync/beacon/worker/worker_desc.nim | 81 +++++++++++++++++++ 9 files changed, 241 insertions(+), 41 deletions(-) diff --git a/execution_chain/sync/beacon.nim b/execution_chain/sync/beacon.nim index 00bf7cf08f..d679985c8f 100644 --- a/execution_chain/sync/beacon.nim +++ b/execution_chain/sync/beacon.nim @@ -15,8 +15,10 @@ import pkg/stew/[interval_set, sorted_set], ../core/chain, ../networking/p2p, - ./beacon/worker/headers/headers_target, ./beacon/[beacon_desc, worker], + ./beacon/worker/blocks/[blocks_fetch, blocks_import], + ./beacon/worker/headers/[headers_fetch, headers_target], + ./beacon/worker/update, ./[sync_sched, wire_protocol] export @@ -25,33 +27,62 @@ export logScope: topics = "beacon sync" +# ------------------------------------------------------------------------------ +# Interceptable handlers +# ------------------------------------------------------------------------------ + +proc schedDaemonCB( + ctx: BeaconCtxRef; + ): Future[Duration] + {.async: (raises: []).} = + return worker.runDaemon(ctx, "RunDaemon") # async/template + +proc schedStartCB(buddy: BeaconBuddyRef): bool = + return worker.start(buddy, "RunStart") + +proc schedStopCB(buddy: BeaconBuddyRef) = + worker.stop(buddy, "RunStop") + +proc schedPoolCB(buddy: BeaconBuddyRef; last: bool; laps: int): bool = + return worker.runPool(buddy, last, laps, "RunPool") + +proc schedPeerCB( + buddy: BeaconBuddyRef; + ): Future[Duration] + {.async: (raises: []).} = + return worker.runPeer(buddy, "RunPeer") # async/template + +proc noOpFn(buddy: BeaconBuddyRef) = discard +proc noOpEx(self: BeaconHandlersSyncRef) = discard + # ------------------------------------------------------------------------------ # Virtual methods/interface, `mixin` functions # ------------------------------------------------------------------------------ proc runSetup(ctx: BeaconCtxRef): bool = - worker.setup(ctx, "RunSetup") + return worker.setup(ctx, "RunSetup") proc runRelease(ctx: BeaconCtxRef) = worker.release(ctx, "RunRelease") -proc runDaemon(ctx: BeaconCtxRef): Future[Duration] {.async: (raises: []).} = - return worker.runDaemon(ctx, "RunDaemon") - proc runTicker(ctx: BeaconCtxRef) = worker.runTicker(ctx, "RunTicker") + +proc runDaemon(ctx: BeaconCtxRef): Future[Duration] {.async: (raises: []).} = + return await ctx.handler.schedDaemon(ctx) + proc runStart(buddy: BeaconBuddyRef): bool = - worker.start(buddy, "RunStart") + return buddy.ctx.handler.schedStart(buddy) proc runStop(buddy: BeaconBuddyRef) = - worker.stop(buddy, "RunStop") + buddy.ctx.handler.schedStop(buddy) proc runPool(buddy: BeaconBuddyRef; last: bool; laps: int): bool = - worker.runPool(buddy, last, laps, "RunPool") + return buddy.ctx.handler.schedPool(buddy, last, laps) proc runPeer(buddy: BeaconBuddyRef): Future[Duration] {.async: (raises: []).} = - return worker.runPeer(buddy, "RunPeer") + return await buddy.ctx.handler.schedPeer(buddy) # ------------------------------------------------------------------------------ # Public functions @@ -83,6 +114,25 @@ proc config*( desc.initSync(ethNode, maxPeers) desc.ctx.pool.chain = chain + # Set up handlers so they can be overlayed + desc.ctx.pool.handlers = BeaconHandlersSyncRef( + version: 0, + activate: updateActivateCB, + suspend: updateSuspendCB, + schedDaemon: schedDaemonCB, + schedStart: schedStartCB, + schedStop: schedStopCB, + schedPool: schedPoolCB, + schedPeer: schedPeerCB, + getBlockHeaders: getBlockHeadersCB, + syncBlockHeaders: noOpFn, + getBlockBodies: getBlockBodiesCB, + syncBlockBodies: noOpFn, + importBlock: importBlockCB, + syncImportBlock: noOpFn, + startSync: noOpEx, + stopSync: noOpEx) + if not desc.lazyConfigHook.isNil: desc.lazyConfigHook(desc) desc.lazyConfigHook = nil @@ -99,10 +149,16 @@ proc configTarget*(desc: BeaconSyncRef; hex: string; isFinal: bool): bool = proc start*(desc: BeaconSyncRef): bool = doAssert not desc.ctx.isNil - desc.startSync() + if desc.startSync(): + let w = BeaconHandlersSyncRef(desc.ctx.pool.handlers) + w.startSync(w) + return true + # false proc stop*(desc: BeaconSyncRef) {.async.} = doAssert not desc.ctx.isNil + let w = BeaconHandlersSyncRef(desc.ctx.pool.handlers) + w.stopSync(w) await desc.stopSync() # ------------------------------------------------------------------------------ diff --git a/execution_chain/sync/beacon/beacon_desc.nim b/execution_chain/sync/beacon/beacon_desc.nim index d453c73485..1a98eab957 100644 --- a/execution_chain/sync/beacon/beacon_desc.nim +++ b/execution_chain/sync/beacon/beacon_desc.nim @@ -25,4 +25,10 @@ type ## Instance descriptor, extends scheduler object lazyConfigHook*: BeaconSyncConfigHook + BeaconHandlersSyncRef* = ref object of BeaconHandlersRef + ## Add start/stop helpers to function list. By default, this functiona + ## are no-ops. + startSync*: proc(self: BeaconHandlersSyncRef) {.gcsafe, raises: [].} + stopSync*: proc(self: BeaconHandlersSyncRef) {.gcsafe, raises: [].} + # End diff --git a/execution_chain/sync/beacon/worker/blocks/blocks_blocks.nim b/execution_chain/sync/beacon/worker/blocks/blocks_blocks.nim index b1508b5fe4..b795f31a05 100644 --- a/execution_chain/sync/beacon/worker/blocks/blocks_blocks.nim +++ b/execution_chain/sync/beacon/worker/blocks/blocks_blocks.nim @@ -17,12 +17,27 @@ import ../../../../networking/p2p, ../../../wire_protocol/types, ../[update, worker_desc], - ./[blocks_fetch, blocks_helpers, blocks_import, blocks_unproc] + ./[blocks_fetch, blocks_helpers, blocks_unproc] # ------------------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------------------ +template importBlock( + buddy: BeaconBuddyRef; + blk: EthBlock; + effPeerID: Hash; + ): Result[Duration,BeaconError] = + ## Async/template + ## + ## Wrapper around `importBlock()` handler + ## + let + ctx = buddy.ctx + rc = await ctx.handler.importBlock(buddy, blk, effPeerID) + ctx.handler.syncImportBlock(buddy) # debugging, trace, replay + rc + proc getNthHash(ctx: BeaconCtxRef; blocks: seq[EthBlock]; n: int): Hash32 = ctx.hdrCache.getHash(blocks[n].header.number).valueOr: return zeroHash32 @@ -201,7 +216,7 @@ template blocksImport*( for n in 0 ..< blocks.len: let nBn = blocks[n].header.number - discard (await buddy.importBlock(blocks[n], peerID)).valueOr: + buddy.importBlock(blocks[n], peerID).isOkOr: if error.excp != ECancelledError: isError = true diff --git a/execution_chain/sync/beacon/worker/blocks/blocks_fetch.nim b/execution_chain/sync/beacon/worker/blocks/blocks_fetch.nim index 9d2f3ac49d..0e75599fb7 100644 --- a/execution_chain/sync/beacon/worker/blocks/blocks_fetch.nim +++ b/execution_chain/sync/beacon/worker/blocks/blocks_fetch.nim @@ -18,11 +18,30 @@ import ../worker_desc, ./blocks_helpers +logScope: + topics = "beacon sync" + +# ------------------------------------------------------------------------------ +# Private helper +# ----------------------------------------------------------------------------- + +template getBlockBodies( + buddy: BeaconBuddyRef; + req: BlockBodiesRequest; + ): Result[FetchBodiesData,BeaconError] = + ## Async/template + ## + ## Wrapper around `getBlockBodies()` handler + ## + let rc = await buddy.ctx.handler.getBlockBodies(buddy, req) + buddy.ctx.handler.syncBlockBodies(buddy) # debugging, sync, replay + rc + # ------------------------------------------------------------------------------ -# Private helpers +# Public handler # ----------------------------------------------------------------------------- -proc getBlockBodies( +proc getBlockBodiesCB*( buddy: BeaconBuddyRef; req: BlockBodiesRequest; ): Future[Result[FetchBodiesData,BeaconError]] @@ -70,7 +89,7 @@ template fetchBodies*( trace trEthSendSendingGetBlockBodies, peer, nReq, bdyErrors=buddy.bdyErrors - let rc = await buddy.getBlockBodies(request) + let rc = buddy.getBlockBodies(request) var elapsed: Duration if rc.isOk: elapsed = rc.value.elapsed diff --git a/execution_chain/sync/beacon/worker/blocks/blocks_import.nim b/execution_chain/sync/beacon/worker/blocks/blocks_import.nim index 433ea7d7ac..343c80f25e 100644 --- a/execution_chain/sync/beacon/worker/blocks/blocks_import.nim +++ b/execution_chain/sync/beacon/worker/blocks/blocks_import.nim @@ -16,11 +16,14 @@ import ../../../wire_protocol, ../worker_desc +logScope: + topics = "beacon sync" + # ------------------------------------------------------------------------------ -# Public function +# Public handler # ------------------------------------------------------------------------------ -proc importBlock*( +proc importBlockCB*( buddy: BeaconBuddyRef; blk: EthBlock; effPeerID: Hash; diff --git a/execution_chain/sync/beacon/worker/headers/headers_fetch.nim b/execution_chain/sync/beacon/worker/headers/headers_fetch.nim index 3d48732bdb..62834a0801 100644 --- a/execution_chain/sync/beacon/worker/headers/headers_fetch.nim +++ b/execution_chain/sync/beacon/worker/headers/headers_fetch.nim @@ -18,11 +18,30 @@ import ../worker_desc, ./headers_helpers +logScope: + topics = "beacon sync" + # ------------------------------------------------------------------------------ # Private helpers +# ----------------------------------------------------------------------------- + +template getBlockHeaders( + buddy: BeaconBuddyRef; + req: BlockHeadersRequest; + ): Result[FetchHeadersData,BeaconError] = + ## Async/template + ## + ## Wrapper around `getBlockHeaders()` handler + ## + let rc = await buddy.ctx.handler.getBlockHeaders(buddy, req) + buddy.ctx.handler.syncBlockHeaders(buddy) # debugging, sync, replay + rc + +# ------------------------------------------------------------------------------ +# Public handler # ------------------------------------------------------------------------------ -proc getBlockHeaders( +proc getBlockHeadersCB*( buddy: BeaconBuddyRef; req: BlockHeadersRequest; ): Future[Result[FetchHeadersData,BeaconError]] @@ -88,7 +107,7 @@ template fetchHeadersReversed*( trace trEthSendSendingGetBlockHeaders & " reverse", peer, req=ivReq, nReq=req.maxResults, hash=topHash.toStr, hdrErrors=buddy.hdrErrors - let rc = await buddy.getBlockHeaders(req) + let rc = buddy.getBlockHeaders(req) var elapsed: Duration if rc.isOk: elapsed = rc.value.elapsed diff --git a/execution_chain/sync/beacon/worker/start_stop.nim b/execution_chain/sync/beacon/worker/start_stop.nim index 6b1d3c70bd..bf8f01ec0e 100644 --- a/execution_chain/sync/beacon/worker/start_stop.nim +++ b/execution_chain/sync/beacon/worker/start_stop.nim @@ -14,7 +14,7 @@ import pkg/[chronicles, chronos, eth/common, metrics], ../../../networking/p2p, ../../wire_protocol, - ./[blocks, headers, update, worker_desc] + ./[blocks, headers, worker_desc] type SyncStateData = tuple @@ -59,8 +59,8 @@ proc setupServices*(ctx: BeaconCtxRef; info: static[string]) = # Set up the notifier informing when a new syncer session has started. ctx.hdrCache.start proc() = - # Activates the syncer. Work will be picked up by peers when available. - ctx.updateActivateSyncer() + # This directive captures `ctx` for calling the activation handler. + ctx.handler.activate(ctx) # Provide progress info call back handler ctx.pool.chain.com.beaconSyncerProgress = proc(): SyncStateData = diff --git a/execution_chain/sync/beacon/worker/update.nim b/execution_chain/sync/beacon/worker/update.nim index 2bb42a0460..343796cf88 100644 --- a/execution_chain/sync/beacon/worker/update.nim +++ b/execution_chain/sync/beacon/worker/update.nim @@ -30,23 +30,6 @@ declareGauge nec_sync_head, "" & # Private functions, state handler helpers # ------------------------------------------------------------------------------ -proc updateSuspendSyncer(ctx: BeaconCtxRef) = - ## Clean up sync target buckets, stop syncer activity, and and get ready - ## for awaiting a new request from the `CL`. - ## - ctx.hdrCache.clear() - - ctx.pool.failedPeers.clear() - ctx.pool.seenData = false - - ctx.hibernate = true - - metrics.set(nec_sync_last_block_imported, 0) - metrics.set(nec_sync_head, 0) - - info "Suspending syncer", base=ctx.chain.baseNumber.bnStr, - head=ctx.chain.latestNumber.bnStr, nSyncPeers=ctx.pool.nBuddies - proc commitCollectHeaders(ctx: BeaconCtxRef; info: static[string]): bool = ## Link header chain into `FC` module. Gets ready for block import. ## @@ -227,7 +210,7 @@ proc updateSyncState*(ctx: BeaconCtxRef; info: static[string]) = # Final sync scrum layout reached or inconsistent/impossible state if newState == idle: - ctx.updateSuspendSyncer() + ctx.handler.suspend(ctx) proc updateLastBlockImported*(ctx: BeaconCtxRef; bn: BlockNumber) = @@ -238,7 +221,7 @@ proc updateLastBlockImported*(ctx: BeaconCtxRef; bn: BlockNumber) = # Public functions, call-back handler ready # ------------------------------------------------------------------------------ -proc updateActivateSyncer*(ctx: BeaconCtxRef) = +proc updateActivateCB*(ctx: BeaconCtxRef) = ## If in hibernate mode, accept a cache session and activate syncer ## if ctx.hibernate and # only in idle mode @@ -277,6 +260,24 @@ proc updateActivateSyncer*(ctx: BeaconCtxRef) = head=ctx.chain.latestNumber.bnStr, state=ctx.hdrCache.state, initTarget=ctx.pool.initTarget.isSome(), nSyncPeers=ctx.pool.nBuddies + +proc updateSuspendCB*(ctx: BeaconCtxRef) = + ## Clean up sync target buckets, stop syncer activity, and and get ready + ## for a new sync request from the `CL`. + ## + ctx.hdrCache.clear() + + ctx.pool.failedPeers.clear() + ctx.pool.seenData = false + + ctx.hibernate = true + + metrics.set(nec_sync_last_block_imported, 0) + metrics.set(nec_sync_head, 0) + + info "Suspending syncer", base=ctx.chain.baseNumber.bnStr, + head=ctx.chain.latestNumber.bnStr, nSyncPeers=ctx.pool.nBuddies + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/execution_chain/sync/beacon/worker/worker_desc.nim b/execution_chain/sync/beacon/worker/worker_desc.nim index 5f7e7dbb29..5acb9af903 100644 --- a/execution_chain/sync/beacon/worker/worker_desc.nim +++ b/execution_chain/sync/beacon/worker/worker_desc.nim @@ -58,6 +58,64 @@ type # ------------------- + ActivateSyncerHdl* = + proc(ctx: BeaconCtxRef) {.gcsafe, raises: [].} + ## Syncer activation function run when notified by header chain cache. + + SuspendSyncerHdl* = proc(ctx: BeaconCtxRef) {.gcsafe, raises: [].} + ## Syncer hibernate function run when the current session fas finished. + + SchedDaemonHdl* = + proc(ctx: BeaconCtxRef): Future[Duration] {.async: (raises: []).} + ## See `runDaemon()` described in `sync_sched.nim` + + SchedStartHdl* = + proc(buddy: BeaconBuddyRef): bool {.gcsafe, raises: [].} + ## See `runStart()` described in `sync_sched.nim` + + SchedStopHdl* = + proc(buddy: BeaconBuddyRef) {.gcsafe, raises: [].} + ## See `runStart()` described in `sync_sched.nim` + + SchedPoolHdl* = + proc(buddy: BeaconBuddyRef; last: bool; laps: int): + bool {.gcsafe, raises: [].} + ## See `runPool()` described in `sync_sched.nim` + + SchedPeerHdl* = + proc(buddy: BeaconBuddyRef): Future[Duration] {.async: (raises: []).} + ## See `runPeer()` described in `sync_sched.nim` + + GetBlockHeadersHdl* = + proc(buddy: BeaconBuddyRef; req: BlockHeadersRequest): + Future[Result[FetchHeadersData,BeaconError]] {.async: (raises: []).} + ## From the ethXX argument peer implied by `buddy` fetch a list of + ## headers. + + SyncBlockHeadersHdl* = + proc(buddy: BeaconBuddyRef) {.gcsafe, raises: [].} + ## Status of syncer after `GetBlockHeadersHdl` + + GetBlockBodiesHdl* = + proc(buddy: BeaconBuddyRef; request: BlockBodiesRequest): + Future[Result[FetchBodiesData,BeaconError]] {.async: (raises: []).} + ## Fetch bodies from the network. + + SyncBlockBodiesHdl* = + proc(buddy: BeaconBuddyRef) {.gcsafe, raises: [].} + ## Status of syncer after `GetBlockBodiesHdl` + + ImportBlockHdl* = + proc(buddy: BeaconBuddyRef; blk: EthBlock; effPeerID: Hash): + Future[Result[Duration,BeaconError]] {.async: (raises: []).} + ## Import a sinmgle block into `FC` module. + + SyncImportBlockHdl* = + proc(buddy: BeaconBuddyRef) {.gcsafe, raises: [].} + ## Status of syncer after `ImportBlockHdl` + + # ------------------- + BnRangeSet* = IntervalSetRef[BlockNumber,uint64] ## Disjunct sets of block number intervals @@ -127,6 +185,24 @@ type hash: Hash32 ## Some block hash to sync towards to isFinal: bool ## The `hash` belongs to a finalised block + BeaconHandlersRef* = ref object of RootRef + ## Selected handlers that can be replaced for tracing. The version number + ## allows to identify overlays. + version*: int ## Overlay version unless 0 (i.e. base=0) + activate*: ActivateSyncerHdl ## Allows for redirect (e.g. tracing) + suspend*: SuspendSyncerHdl ## Ditto + schedDaemon*: SchedDaemonHdl ## ... + schedStart*: SchedStartHdl + schedStop*: SchedStopHdl + schedPool*: SchedPoolHdl + schedPeer*: SchedPeerHdl + getBlockHeaders*: GetBlockHeadersHdl + syncBlockHeaders*: SyncBlockHeadersHdl + getBlockBodies*: GetBlockBodiesHdl + syncBlockBodies*: SyncBlockBodiesHdl + importBlock*: ImportBlockHdl + syncImportBlock*: SyncImportBlockHdl + BeaconCtxData* = object ## Globally shared data extension nBuddies*: int ## Number of active workers @@ -139,6 +215,7 @@ type chain*: ForkedChainRef ## Core database, FCU support hdrCache*: HeaderChainRef ## Currently in tandem with `chain` + handlers*: BeaconHandlersRef ## Allows for redirect (e.g. tracing) # Info, debugging, and error handling stuff nProcError*: Table[Hash,BuddyError] ## Per peer processing error @@ -176,6 +253,10 @@ func hdrCache*(ctx: BeaconCtxRef): HeaderChainRef = ## Shortcut ctx.pool.hdrCache +func handler*(ctx: BeaconCtxRef): BeaconHandlersRef = + ## Shortcut + ctx.pool.handlers + # ----- func hibernate*(ctx: BeaconCtxRef): bool = From e20495163246312a1a09cefd4f4ef3973ab65667 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Wed, 23 Jul 2025 10:47:29 +0100 Subject: [PATCH 02/18] Provide tracer framework with intercepting syncer session handlers --- tools/syncer/trace/trace_desc.nim | 215 ++++++++++++++++++ tools/syncer/trace/trace_setup.nim | 179 +++++++++++++++ .../syncer/trace/trace_setup/setup_blocks.nim | 130 +++++++++++ .../trace/trace_setup/setup_headers.nim | 71 ++++++ .../trace/trace_setup/setup_helpers.nim | 84 +++++++ .../syncer/trace/trace_setup/setup_sched.nim | 177 ++++++++++++++ tools/syncer/trace/trace_setup/setup_sync.nim | 71 ++++++ .../syncer/trace/trace_setup/setup_write.nim | 151 ++++++++++++ 8 files changed, 1078 insertions(+) create mode 100644 tools/syncer/trace/trace_desc.nim create mode 100644 tools/syncer/trace/trace_setup.nim create mode 100644 tools/syncer/trace/trace_setup/setup_blocks.nim create mode 100644 tools/syncer/trace/trace_setup/setup_headers.nim create mode 100644 tools/syncer/trace/trace_setup/setup_helpers.nim create mode 100644 tools/syncer/trace/trace_setup/setup_sched.nim create mode 100644 tools/syncer/trace/trace_setup/setup_sync.nim create mode 100644 tools/syncer/trace/trace_setup/setup_write.nim diff --git a/tools/syncer/trace/trace_desc.nim b/tools/syncer/trace/trace_desc.nim new file mode 100644 index 0000000000..4c36cead96 --- /dev/null +++ b/tools/syncer/trace/trace_desc.nim @@ -0,0 +1,215 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Trace environment descriptor and helpers +## +## TODO: +## * n/a +## + +{.push raises:[].} + +import + std/[net, streams], + pkg/[chronos, eth/common], + ../../../execution_chain/sync/wire_protocol, + ../../../execution_chain/sync/beacon/beacon_desc, + ../../../execution_chain/sync/beacon/worker/worker_desc + +export + beacon_desc, + worker_desc + +const + TraceVersionID* = 20250828 + + TraceSetupID* = 1 ## Phase 1 layout ID, prepare + TraceRunnerID* = 10 ## Phase 2 layout ID, full execution + +type + StopIfEosHdl* = proc(trc: TraceRef) {.gcsafe, raises: [].} + ## Terminate trace if the number of sessions is exhausted + + TraceRef* = ref object of BeaconHandlersSyncRef + ## Overlay handlers extended by descriptor data + ctx*: BeaconCtxRef ## Parent context + outStream*: Stream ## Output file with capture records + backup*: BeaconHandlersRef ## Can restore previous handlers + started*: Moment ## Start time + sessions*: int ## Initial number of sessions + remaining*: int ## Number of sessions left to run + stopIfEos*: StopIfEosHdl ## Auto-disable trace when needed + serial: uint ## Unique record ID + + # ------------- + + TraceRecType* = enum + TrtOops = 0 + TrtVersionInfo = 1 + + TrtSyncActvFailed + TrtSyncActivated + TrtSyncHibernated + + TrtSchedDaemonBegin + TrtSchedDaemonEnd + TrtSchedStart + TrtSchedStop + TrtSchedPool + TrtSchedPeerBegin + TrtSchedPeerEnd + + TrtFetchHeaders + TrtSyncHeaders + + TrtFetchBodies + TrtSyncBodies + + TrtImportBlock + TrtSyncBlock + + TraceRecBase* = object of RootObj + ## Trace context applicable with and without known peer + time*: Duration ## Relative to `TraceRef.started` + serial*: uint ## Increasing serial number + frameID*: uint ## Begin/end frame + nPeers*: uint + syncState*: SyncState + chainMode*: HeaderChainMode + poolMode*: bool + + baseNum*: BlockNumber ## Max finalised number from `FC` module + latestNum*: BlockNumber ## Number of latest branch head + antecedent*: BlockNumber ## Lower end of header chain cache + + hdrUnprLen*: uint64 ## # unprocessed header entries + hdrUnprChunks*: uint ## # unprocessed header iv segments + hdrUnprLast*: BlockNumber ## last avail block number + hdrUnprLastLen*: uint64 ## size of last block number interval + + blkUnprLen*: uint64 ## # unprocessed block entries + blkUnprChunks*: uint ## # unprocessed block iv segments + blkUnprLeast*: BlockNumber ## least avail block number + blkUnprLeastLen*: uint64 ## size of first interval + + stateAvail*: int ## Bitmask: 1=peerCtrl, 2=peerID, etc. + peerCtrl*: BuddyRunState ## 1) Rlp encoded `Opt[seq[xxx]]` would + peerID*: Hash ## 2) .. need manual decoder/reader + nHdrErrors*: uint8 ## 4) # header comm. errors + nBlkErrors*: uint8 ## 8) # body comm. errors + slowPeer*: Hash ## 16) Registered slow peer + + + TraceVersionInfo* = object of TraceRecBase + version*: uint + networkId*: NetworkId + + # ------------- + + TraceSyncActvFailed* = object of TraceRecBase + + TraceSyncActivated* = object of TraceRecBase + head*: Header ## Part of environment + finHash*: Hash32 ## Part of environment + + TraceSyncHibernated* = object of TraceRecBase + + # ------------- + + TraceSchedDaemonBegin* = object of TraceRecBase + ## Environment is captured before the daemon handler body is executed. + + TraceSchedDaemonEnd* = object of TraceRecBase + ## Environment is captured when leaving the daemon handler. + idleTime*: Duration ## Suggested idle time + + TraceSchedStart* = object of TraceRecBase + ## Environment is captured when leaving sched the start handler. + peerIP*: IpAddress ## Descriptor argument + peerPort*: Port ## Descriptor argument + accept*: bool ## Result/return code + + TraceSchedStop* = object of TraceRecBase + ## Environment is captured when leaving the sched stop handler. + peerIP*: IpAddress ## Descriptor argument + peerPort*: Port ## Descriptor argument + + TraceSchedPool* = object of TraceRecBase + ## Environment is captured leaving the pool handler. + peerIP*: IpAddress ## Descriptor argument + peerPort*: Port ## Descriptor argument + last*: bool ## Request argument + laps*: uint ## Request argument + stop*: bool ## Result/return code + + TraceSchedPeerBegin* = object of TraceRecBase + ## Environment is captured before the peer handler body is executed. + peerIP*: IpAddress ## Descriptor argument + peerPort*: Port ## Descriptor argument + + TraceSchedPeerEnd* = object of TraceRecBase + ## Environment is captured when leaving peer handler. + idleTime*: Duration ## Suggested idle time + + # ------------- + + TraceFetchHeaders* = object of TraceRecBase + ## Environment is captured after the `getBlockHeaders()` handler is run. + req*: BlockHeadersRequest ## Fetch request + ivReq*: BnRange ## Request as interval of block numbers + fieldAvail*: uint ## Bitmask: 1=fetched, 2=error + fetched*: FetchHeadersData ## If dowloaded successfully + error*: BeaconError + + TraceSyncHeaders* = object of TraceRecBase + ## Environment is captured when the `syncBlockHeaders()` handler is run. + + + TraceFetchBodies* = object of TraceRecBase + ## Environment is captured after the `getBlockBodies()` handler is run. + req*: BlockBodiesRequest ## Fetch request + ivReq*: BnRange ## Request as interval of block numbers + fieldAvail*: uint ## Bitmask: 1=fetchd, 2=error + fetched*: FetchBodiesData ## If dowloaded successfully + error*: BeaconError + + TraceSyncBodies* = object of TraceRecBase + ## Environment is captured when the `syncBlockBodies()` handler is run. + + + TraceImportBlock* = object of TraceRecBase + ## Environment is captured after the `importBlock()` handler is run. + ethBlock*: EthBlock ## Request argument + effPeerID*: Hash ## Request argument + fieldAvail*: uint ## Bitmask: 1=elapsed, 2=error + elapsed*: Duration ## Processing time on success + error*: BeaconError + + TraceSyncBlock* = object of TraceRecBase + ## Environment is captured after the `syncImportBlock()` handler is run. + +# ------------------------------------------------------------------------------ +# Public helpers +# ------------------------------------------------------------------------------ + +func trace*(ctx: BeaconCtxRef): TraceRef = + ## Getter, get trace descriptor (if any) + if ctx.handler.version == TraceRunnerID: + return ctx.handler.TraceRef + +func newSerial*(trc: TraceRef): uint64 = + trc.serial.inc + if trc.serial == 0: + trc.serial.inc + trc.serial + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup.nim b/tools/syncer/trace/trace_setup.nim new file mode 100644 index 0000000000..817bfcecde --- /dev/null +++ b/tools/syncer/trace/trace_setup.nim @@ -0,0 +1,179 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Trace environment setup & destroy + +{.push raises:[].} + +import + std/[os, streams, syncio], + pkg/[chronicles, chronos], + ./trace_setup/[ + setup_blocks, setup_headers, setup_helpers, setup_sched, setup_sync, + setup_write], + ./trace_desc + +logScope: + topics = "beacon trace" + +const + DontQuit = low(int) + ## To be used with `onCloseException()` + + stopInfo = "traceStop(): " + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +template onException( + info: static[string]; + quitCode: static[int]; + code: untyped) = + try: + code + except CatchableError as e: + const blurb = info & "Trace stream exception" + when quitCode == DontQuit: + error blurb, error=($e.name), msg=e.msg + else: + fatal blurb & " -- STOP", error=($e.name), msg=e.msg + quit(quitCode) + +# ----------- + +proc stopIfEos(trc: TraceRef) = + trc.remaining.dec + if trc.remaining <= 0: + info stopInfo & "Number of sessions exhausted", nSessions=trc.sessions + trc.stopSync(trc) + +proc writeVersion(ctx: BeaconCtxRef) = + var tRec: TraceVersionInfo + tRec.init ctx + tRec.version = TraceVersionID + tRec.networkId = ctx.chain.com.networkId + ctx.traceWrite tRec + trace "=Version", TraceVersionID, serial=tRec.serial + +# ----------- + +proc traceStartCB(trc: TraceRef) = + ## Start trace session handler + ## + trc.started = Moment.now() + trc.stopIfEos = stopIfEos + + # Set up redirect handlers for trace/capture + trc.version = TraceRunnerID + trc.activate = activateTrace + trc.suspend = suspendTrace + trc.schedDaemon = schedDaemonTrace + trc.schedStart = schedStartTrace + trc.schedStop = schedStopTrace + trc.schedPool = schedPoolTrace + trc.schedPeer = schedPeerTrace + trc.getBlockHeaders = fetchHeadersTrace + trc.syncBlockHeaders = syncHeadersTrace + trc.getBlockBodies = fetchBodiesTrace + trc.syncBlockBodies = syncBodiesTrace + trc.importBlock = importBlockTrace + trc.syncImportBlock = syncBlockTrace + + trc.startSync = proc(self: BeaconHandlersSyncRef) = + discard + + trc.stopSync = proc(self: BeaconHandlersSyncRef) = + stopInfo.onException(DontQuit): + TraceRef(self).outStream.flush() + TraceRef(self).outStream.close() + TraceRef(self).ctx.pool.handlers = TraceRef(self).backup + + # Write version as first record + trc.ctx.writeVersion() + +# ------------------------------------------------------------------------------ +# Public constructor/destructor +# ------------------------------------------------------------------------------ + +proc traceSetup*( + ctx: BeaconCtxRef; + fileName: string; + nSessions: int; + ): Result[void,string] = + ## Install trace handlers + const info = "traceSetup(): " + + if ctx.handler.version != 0: + return err("Overlay session handlers activated already" & + ", ID=" & $ctx.handler.version) + + if fileName.fileExists: # File must not exist yet + return err("Unsafe, please delete file first" & + ", fileName=\"" & fileName & "\"") + + var strm = Stream(nil) + info.onException(DontQuit): + # Note that there is a race condition. The proper open mode shoud be + # `fmReadWriteExisting` (sort of resembling `O_CREATE|O_EXCL`) but it + # does not work with the current nim version `2.2.4`. + var fd: File + if fd.open(fileName, fmWrite): + strm = fd.newFileStream() + + if strm.isNil: + return err("Cannot open trace file for writing" & + ", fileName=\"" & fileName & "\"") + + let trc = TraceRef( + # Install new extended handler descriptor + ctx: ctx, + outStream: strm, + backup: ctx.pool.handlers, + sessions: nSessions, + remaining: nSessions, + + # This is still the old descriptor which will be updated when + # `startSync()` is run. + version: TraceSetupID, + activate: ctx.handler.activate, + suspend: ctx.handler.suspend, + schedDaemon: ctx.handler.schedDaemon, + schedStart: ctx.handler.schedStart, + schedStop: ctx.handler.schedStop, + schedPool: ctx.handler.schedPool, + schedPeer: ctx.handler.schedPeer, + getBlockHeaders: ctx.handler.getBlockHeaders, + syncBlockHeaders: ctx.handler.syncBlockHeaders, + getBlockBodies: ctx.handler.getBlockBodies, + syncBlockBodies: ctx.handler.syncBlockBodies, + importBlock: ctx.handler.importBlock, + syncImportBlock: ctx.handler.syncImportBlock) + + trc.startSync = proc(self: BeaconHandlersSyncRef) = + TraceRef(self).traceStartCB() + + trc.stopSync = proc(self: BeaconHandlersSyncRef) = + info.onException(DontQuit): + TraceRef(self).outStream.close() + TraceRef(self).ctx.pool.handlers = TraceRef(self).backup + + ctx.pool.handlers = trc + ok() + + +proc traceRelease*(ctx: BeaconCtxRef) = + ## Stop tracer and restore descriptors + if ctx.pool.handlers.version in {TraceSetupID, TraceRunnerID}: + TraceRef(ctx.pool.handlers).stopSync(nil) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_blocks.nim b/tools/syncer/trace/trace_setup/setup_blocks.nim new file mode 100644 index 0000000000..4afa4e70be --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_blocks.nim @@ -0,0 +1,130 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Overlay handler for trace environment + +{.push raises:[].} + +import + pkg/[chronicles, chronos, stew/interval_set], + ../../../../execution_chain/networking/p2p, + ../../../../execution_chain/sync/wire_protocol/types, + ../trace_desc, + ./[setup_helpers, setup_write] + +logScope: + topics = "beacon trace" + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc toBnRange( + ctx: BeaconCtxRef; + lst: openArray[Hash32]; + info: static[string]; + ): BnRange = + ## Resolve block hashes as interval of block numbers + let rs = BnRangeSet.init() + for w in lst: + let h = ctx.hdrCache.get(w).valueOr: + raiseAssert info & ": Cannot resolve" & + ", hash=" & w.short + if rs.merge(h.number,h.number) != 1: + raiseAssert info & ": dulplicate hash" & + ", hash=" & w.short & ", number=" & h.bnStr + rs.ge().expect "valid BnRange" + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc fetchBodiesTrace*( + buddy: BeaconBuddyRef; + req: BlockBodiesRequest; + ): Future[Result[FetchBodiesData,BeaconError]] + {.async: (raises: []).} = + ## Replacement for `getBlockBodies()` handler which in addition writes data + ## to the output stream for tracing. + ## + let + ivReq = buddy.ctx.toBnRange(req.blockHashes, "fetchBodiesTrace") + data = await buddy.ctx.trace.backup.getBlockBodies(buddy, req) + + var tRec: TraceFetchBodies + tRec.init buddy + tRec.req = req + tRec.ivReq = ivReq + if data.isOk: + tRec.fieldAvail = 1 + tRec.fetched = data.value + else: + tRec.fieldAvail = 2 + tRec.error = data.error + buddy.traceWrite tRec + + trace "=BodiesFetch", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial, ivReq=ivReq.bnStr + return data + +proc syncBodiesTrace*( + buddy: BeaconBuddyRef; + ) = + ## Replacement for `syncBlockBodies()` handler. + var tRec: TraceSyncBodies + tRec.init buddy + buddy.traceWrite tRec + + trace "=BodiesSync", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial + + +proc importBlockTrace*( + buddy: BeaconBuddyRef; + ethBlock: EthBlock; + effPeerID: Hash; + ): Future[Result[Duration,BeaconError]] + {.async: (raises: []).} = + ## Replacement for `importBlock()` handler which in addition writes data to + ## the output stream for tracing. + ## + let data = await buddy.ctx.trace.backup.importBlock( + buddy, ethBlock, effPeerID) + + var tRec: TraceImportBlock + tRec.init buddy + tRec.ethBlock = ethBlock + tRec.effPeerID = effPeerID + if data.isOk: + tRec.fieldAvail = 1 + tRec.elapsed = data.value + else: + tRec.fieldAvail = 2 + tRec.error = data.error + buddy.traceWrite tRec + + trace "=BlockImport", peer=($buddy.peer), peerID=buddy.peerID.short, + effPeerID=tRec.peerID.short, serial=tRec.serial + return data + +proc syncBlockTrace*( + buddy: BeaconBuddyRef; + ) = + ## Replacement for `syncImportBlock()` handler. + var tRec: TraceSyncBlock + tRec.init buddy + buddy.traceWrite tRec + + trace "=BlockSync", peer=($buddy.peer), peerID=buddy.peerID.short, + effPeerID=tRec.peerID.short, serial=tRec.serial + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_headers.nim b/tools/syncer/trace/trace_setup/setup_headers.nim new file mode 100644 index 0000000000..7089d1014d --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_headers.nim @@ -0,0 +1,71 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Overlay handler for trace environment + +{.push raises:[].} + +import + pkg/[chronicles, chronos, stew/interval_set], + ../../../../execution_chain/networking/p2p, + ../../../../execution_chain/sync/wire_protocol/types, + ../trace_desc, + ./[setup_helpers, setup_write] + +logScope: + topics = "beacon trace" + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc fetchHeadersTrace*( + buddy: BeaconBuddyRef; + req: BlockHeadersRequest; + ): Future[Result[FetchHeadersData,BeaconError]] + {.async: (raises: []).} = + ## Replacement for `getBlockHeaders()` handler which in addition writes data + ## to the output stream for tracing. + ## + let data = await buddy.ctx.trace.backup.getBlockHeaders(buddy, req) + + if not buddy.ctx.hibernate: + var tRec: TraceFetchHeaders + tRec.init buddy + tRec.req = req + if data.isOk: + tRec.fieldAvail = 1 + tRec.fetched = data.value + else: + tRec.fieldAvail = 2 + tRec.error = data.error + buddy.traceWrite tRec + + trace "=HeadersFetch", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial + + return data + +proc syncHeadersTrace*( + buddy: BeaconBuddyRef; + ) = + ## Replacement for `syncBlockHeaders()` handler, + ## + if not buddy.ctx.hibernate: + var tRec: TraceSyncHeaders + tRec.init buddy + buddy.traceWrite tRec + + trace "=HeadersSync", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_helpers.nim b/tools/syncer/trace/trace_setup/setup_helpers.nim new file mode 100644 index 0000000000..f51201f563 --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_helpers.nim @@ -0,0 +1,84 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +{.push raises:[].} + +import + std/[strformat, strutils], + pkg/[chronos, stew/interval_set], + ../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers, + ../trace_desc + +export + worker_helpers + +# ------------------------------------------------------------------------------ +# Public context capture initialisation +# ------------------------------------------------------------------------------ + +proc init*(tb: var TraceRecBase; ctx: BeaconCtxRef) = + ## Initialise new trace descriptor. This fuction does nothing if + ## there is no active trace. + let trc = ctx.trace + if not trc.isNil: + tb.serial = trc.newSerial + tb.time = Moment.now() - trc.started + tb.syncState = ctx.pool.lastState + tb.nPeers = ctx.pool.nBuddies.uint + tb.chainMode = ctx.hdrCache.state + tb.poolMode = ctx.poolMode + tb.baseNum = ctx.chain.baseNumber + tb.latestNum = ctx.chain.latestNumber + tb.antecedent = ctx.hdrCache.antecedent.number + + tb.hdrUnprChunks = ctx.hdr.unprocessed.chunks().uint + if 0 < tb.hdrUnprChunks: + tb.hdrUnprLen = ctx.hdr.unprocessed.total() + let iv = ctx.hdr.unprocessed.le().expect "valid iv" + tb.hdrUnprLast = iv.maxPt + tb.hdrUnprLastLen = iv.len + + tb.blkUnprChunks = ctx.blk.unprocessed.chunks().uint + if 0 < tb.blkUnprChunks: + tb.blkUnprLen = ctx.blk.unprocessed.total() + let iv = ctx.blk.unprocessed.ge().expect "valid iv" + tb.blkUnprLeast = iv.minPt + tb.blkUnprLeastLen = iv.len + + if ctx.pool.lastSlowPeer.isOk(): + tb.stateAvail = 16 + tb.slowPeer = ctx.pool.lastSlowPeer.value + else: + tb.stateAvail = 0 + +proc init*(tb: var TraceRecBase; buddy: BeaconBuddyRef) = + ## Variant of `init()` for `buddy` rather than `ctx` + let + ctx = buddy.ctx + trc = ctx.trace + if not trc.isNil: + tb.init ctx + tb.stateAvail += 15 + tb.peerCtrl = buddy.ctrl.state + tb.peerID = buddy.peerID + tb.nHdrErrors = buddy.only.nRespErrors.hdr + tb.nBlkErrors = buddy.only.nRespErrors.blk + +# -------------- + +func short*(w: Hash): string = + w.toHex(8).toLowerAscii # strips leading 8 bytes + +func idStr*(w: uint64): string = + &"{w:x}" + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_sched.nim b/tools/syncer/trace/trace_setup/setup_sched.nim new file mode 100644 index 0000000000..6b69168b5a --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_sched.nim @@ -0,0 +1,177 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Overlay handler for trace environment + +{.push raises:[].} + +import + pkg/[chronicles, chronos], + ../../../../execution_chain/networking/p2p, + ../trace_desc, + ./[setup_helpers, setup_write] + +logScope: + topics = "beacon trace" + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc getIP(buddy: BeaconBuddyRef): IpAddress = + buddy.peer.remote.node.address.ip + +proc getPort(buddy: BeaconBuddyRef): Port = + let peer = buddy.peer + if peer.remote.node.address.tcpPort != Port(0): + peer.remote.node.address.tcpPort + else: + peer.remote.node.address.udpPort + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc schedDaemonTrace*( + ctx: BeaconCtxRef; + ): Future[Duration] + {.async: (raises: []).} = + ## Replacement for `schedDaemon()` handler which in addition + ## write data to the output stream for tracing. + ## + var tBeg: TraceSchedDaemonBegin + tBeg.init ctx + tBeg.frameID = tBeg.serial + ctx.traceWrite tBeg + + trace "+Daemon", serial=tBeg.serial, frameID=tBeg.frameID.idStr, + syncState=tBeg.syncState + + let idleTime = await ctx.trace.backup.schedDaemon ctx + + var tEnd: TraceSchedDaemonEnd + tEnd.init ctx + tEnd.frameID = tBeg.serial # refers back to `tBeg` capture + tEnd.idleTime = idleTime + ctx.traceWrite tEnd + + if 0 < tEnd.serial: + trace "-Daemon", serial=tEnd.serial, frameID=tEnd.frameID.idStr, + syncState=tBeg.syncState, idleTime=idleTime.toStr + else: + trace "-Daemon (blind)", serial="n/a", frameID=tEnd.frameID.idStr, + syncState=tBeg.syncState, idleTime=idleTime.toStr + + return idleTime + + +proc schedStartTrace*(buddy: BeaconBuddyRef): bool = + ## Similar to `schedDaemonTrace()` + ## + let + ctx = buddy.ctx + acceptOk = ctx.trace.backup.schedStart(buddy) + + if not ctx.hibernate: + var tRec: TraceSchedStart + tRec.init buddy + tRec.frameID = tRec.serial + tRec.peerIP = buddy.getIP() + tRec.peerPort = buddy.getPort() + tRec.accept = acceptOk + buddy.traceWrite tRec + + trace "=StartPeer", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial, frameID=tRec.frameID.idStr, + syncState=tRec.syncState + + acceptOk + + +proc schedStopTrace*(buddy: BeaconBuddyRef) = + ## Similar to `schedDaemonTrace()` + ## + let ctx = buddy.ctx + + ctx.trace.backup.schedStop(buddy) + + if not ctx.hibernate: + var tRec: TraceSchedStop + tRec.init buddy + tRec.frameID = tRec.serial + tRec.peerIP = buddy.getIP() + tRec.peerPort = buddy.getPort() + buddy.traceWrite tRec + + trace "=StopPeer", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial, frameID=tRec.frameID.idStr, + syncState=tRec.syncState + + +proc schedPoolTrace*(buddy: BeaconBuddyRef; last: bool; laps: int): bool = + ## Similar to `schedDaemonTrace()` + ## + let stopOk = buddy.ctx.trace.backup.schedPool(buddy, last, laps) + + var tRec: TraceSchedPool + tRec.init buddy + tRec.frameID = tRec.serial + tRec.peerIP = buddy.getIP() + tRec.peerPort = buddy.getPort() + tRec.last = last + tRec.laps = laps.uint + tRec.stop = stopOk + buddy.traceWrite tRec + + trace "=Pool", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tRec.serial, frameID=tRec.frameID.idStr + + stopOk + + +proc schedPeerTrace*( + buddy: BeaconBuddyRef; + ): Future[Duration] + {.async: (raises: []).} = + ## Similar to `schedDaemonTrace()` + ## + let + ctx = buddy.ctx + noisy = not ctx.hibernate + + var tBeg: TraceSchedPeerBegin + if noisy: + tBeg.init buddy + tBeg.frameID = tBeg.serial + tBeg.peerIP = buddy.getIP() + tBeg.peerPort = buddy.getPort() + buddy.traceWrite tBeg + + trace "+Peer", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tBeg.serial, frameID=tBeg.frameID.idStr, syncState=tBeg.syncState + + let idleTime = await ctx.trace.backup.schedPeer(buddy) + + if noisy: + var tEnd: TraceSchedPeerEnd + tEnd.init buddy + tEnd.frameID = tBeg.serial # refers back to `tBeg` capture + tEnd.idleTime = idleTime + buddy.traceWrite tEnd + + trace "-Peer", peer=($buddy.peer), peerID=buddy.peerID.short, + serial=tEnd.serial, frameID=tEnd.frameID.idStr, syncState=tBeg.syncState, + idleTime=idleTime.toStr + + return idleTime + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_sync.nim b/tools/syncer/trace/trace_setup/setup_sync.nim new file mode 100644 index 0000000000..7617d4d778 --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_sync.nim @@ -0,0 +1,71 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Overlay handler for trace environment + +{.push raises:[].} + +import + pkg/chronicles, + ../trace_desc, + ./[setup_helpers, setup_write] + +logScope: + topics = "beacon trace" + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc activateTrace*(ctx: BeaconCtxRef) = + ## Replacement for `activate()` handler which in addition + ## write data to the output stream for tracing. + ## + let hdl = ctx.trace.backup + hdl.activate ctx + + if ctx.hibernate: + var tRec: TraceSyncActvFailed + tRec.init ctx + ctx.traceWrite tRec + + trace "=ActvFailed", serial=tRec.serial + + else: + let chn = ctx.chain + var tRec: TraceSyncActivated + tRec.init ctx + tRec.head = ctx.hdrCache.head + tRec.finHash = chn.finHash + ctx.traceWrite tRec + + trace "=Activated", serial=tRec.serial + + +proc suspendTrace*(ctx: BeaconCtxRef) = + ## Replacement for `suspend()` handler which in addition writes + ## data to the output stream for tracing. + ## + let hdl = ctx.trace.backup + hdl.suspend ctx + + var tRec: TraceSyncHibernated + tRec.init ctx + ctx.traceWrite tRec + + trace "=Suspended", serial=tRec.serial + + let trc = ctx.trace + if not trc.isNil: + trc.stopIfEos(trc) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_write.nim b/tools/syncer/trace/trace_setup/setup_write.nim new file mode 100644 index 0000000000..652e5cba27 --- /dev/null +++ b/tools/syncer/trace/trace_setup/setup_write.nim @@ -0,0 +1,151 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +{.push raises:[].} + +import + std/[net, streams, typetraits], + pkg/[chronicles, chronos, eth/common, stew/base64], + ../trace_desc + +logScope: + topics = "beacon trace" + +# ------------------------------------------------------------------------------ +# Private mixin helpers for RLP encoder +# ------------------------------------------------------------------------------ + +proc append(w: var RlpWriter, h: Hash) = + when sizeof(h) != sizeof(uint): + # `castToUnsigned()` is defined in `std/private/bitops_utils` and + # included by `std/bitops` but not exported (as of nim 2.2.4) + {.error: "Expected that Hash is based on int".} + w.append(cast[uint](h).uint64) + +proc append(w: var RlpWriter, d: chronos.Duration) = + w.append(cast[uint64](d.nanoseconds)) + +proc append(w: var RlpWriter, p: Port) = + w.append(distinctBase p) + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc toTypeInx(w: TraceRecType): string = + if w.ord < 10: + $w.ord + else: + $chr(w.ord + 'A'.ord - 10) + + +proc toStream( + buddy: BeaconBuddyRef; + trp: TraceRecType; + blob: seq[byte]; + flush = false; + ) = + ## Write tracet data to output stream + let trc = buddy.ctx.trace + if trc.isNil: + debug "Trace output stopped while collecting", + peer=($buddy.peer), recType=trp + else: + try: + trc.outStream.writeLine trp.toTypeInx & " " & Base64.encode(blob) + trc.outStream.flush() + except CatchableError as e: + warn "Error writing trace data", peer=($buddy.peer), recType=trp, + recSize=blob.len, error=($e.name), msg=e.msg + +proc toStream( + ctx: BeaconCtxRef; + trp: TraceRecType; + blob: seq[byte]; + flush = false; + ) = + ## Variant of `toStream()` for `ctx` rather than `buddy` + let trc = ctx.trace + if trc.isNil: + debug "Trace output stopped while collecting", recType=trp + else: + try: + trc.outStream.writeLine trp.toTypeInx & " " & Base64.encode(blob) + trc.outStream.flush() + except CatchableError as e: + warn "Error writing trace data", recType=trp, + recSize=blob.len, error=($e.name), msg=e.msg + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceVersionInfo) = + ctx.toStream(TrtVersionInfo, rlp.encode w) + +# ------------- + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncActvFailed) = + ctx.toStream(TrtSyncActvFailed, rlp.encode w) + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncActivated) = + ctx.toStream(TrtSyncActivated, rlp.encode w) + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncHibernated) = + ctx.toStream(TrtSyncHibernated, rlp.encode w) + +# ------------- + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceSchedDaemonBegin) = + ctx.toStream(TrtSchedDaemonBegin, rlp.encode w) + +proc traceWrite*(ctx: BeaconCtxRef; w: TraceSchedDaemonEnd) = + ctx.toStream(TrtSchedDaemonEnd, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedStart) = + buddy.toStream(TrtSchedStart, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedStop) = + buddy.toStream(TrtSchedStop, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPool) = + buddy.toStream(TrtSchedPool, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPeerBegin) = + buddy.toStream(TrtSchedPeerBegin, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPeerEnd) = + buddy.toStream(TrtSchedPeerEnd, rlp.encode w) + +# ------------- + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceFetchHeaders) = + buddy.toStream(TrtFetchHeaders, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncHeaders) = + buddy.toStream(TrtSyncHeaders, rlp.encode w) + + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceFetchBodies) = + buddy.toStream(TrtFetchBodies, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncBodies) = + buddy.toStream(TrtSyncBodies, rlp.encode w) + + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceImportBlock) = + buddy.toStream(TrtImportBlock, rlp.encode w) + +proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncBlock) = + buddy.toStream(TrtSyncBlock, rlp.encode w) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ From c4066a021d82ddc27e4ada426a211e2f05777b0f Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Thu, 28 Aug 2025 17:08:57 +0100 Subject: [PATCH 03/18] Provide command line tracer tool details This tool wraps and runs the `nimbus_execution_client` while tracing and capturing state data. --- Makefile | 13 +- tools/syncer/helpers/nimbus_el_wrapper.nim | 30 +++ tools/syncer/helpers/sync_ticker.nim | 182 ++++++++++++++++++ tools/syncer/syncer_test_client_trace.nim | 155 +++++++++++++++ tools/syncer/syncer_test_client_trace.nim.cfg | 5 + tools/syncer/trace/README.md | 46 +++++ 6 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 tools/syncer/helpers/nimbus_el_wrapper.nim create mode 100644 tools/syncer/helpers/sync_ticker.nim create mode 100644 tools/syncer/syncer_test_client_trace.nim create mode 100644 tools/syncer/syncer_test_client_trace.nim.cfg create mode 100644 tools/syncer/trace/README.md diff --git a/Makefile b/Makefile index 27732c8afd..07c605dc16 100644 --- a/Makefile +++ b/Makefile @@ -387,8 +387,19 @@ evmstate_test: | build deps evmstate txparse: | build deps $(ENV_SCRIPT) nim c $(NIM_PARAMS) "tools/txparse/$@.nim" +# build syncer debugging and analysis tools +SYNCER_TOOLS_DIR := tools/syncer +SYNCER_TOOLS := $(foreach name,trace,syncer_test_client_$(name)) +.PHONY: syncer-tools syncer-tools-clean $(SYNCER_TOOLS) +syncer-tools: $(SYNCER_TOOLS) +syncer-tools-clean: + rm -f $(foreach exe,$(SYNCER_TOOLS),build/$(exe)) +$(SYNCER_TOOLS): | build deps rocksdb + echo -e $(BUILD_MSG) "build/$@" + $(ENV_SCRIPT) nim c $(NIM_PARAMS) -o:build/$@ "$(SYNCER_TOOLS_DIR)/$@.nim" + # usual cleaning -clean: | clean-common +clean: | clean-common syncer-tools-clean rm -rf build/{nimbus,nimbus_execution_client,nimbus_portal_client,fluffy,portal_bridge,libverifproxy,nimbus_verified_proxy,$(TOOLS_CSV),$(PORTAL_TOOLS_CSV),all_tests,test_kvstore_rocksdb,test_rpc,all_portal_tests,all_history_network_custom_chain_tests,test_portal_testnet,utp_test_app,utp_test,*.dSYM} rm -rf tools/t8n/{t8n,t8n_test} rm -rf tools/evmstate/{evmstate,evmstate_test} diff --git a/tools/syncer/helpers/nimbus_el_wrapper.nim b/tools/syncer/helpers/nimbus_el_wrapper.nim new file mode 100644 index 0000000000..3cccf434bf --- /dev/null +++ b/tools/syncer/helpers/nimbus_el_wrapper.nim @@ -0,0 +1,30 @@ +# This file may not be copied, modified, or distributed except according to +# those terms. + +## Wrapper to expose `run()` from `nimbus_execution_client.nim` without +## marking is exportable. + +include # (!) + ../../../execution_chain/nimbus_execution_client + +proc runNimbusExeClient*(conf: NimbusConf; cfgCB: BeaconSyncConfigHook) = + ## Wrapper, make it public for debugging + ProcessState.setupStopHandlers() + + # Set up logging before everything else + setupLogging(conf.logLevel, conf.logStdout, none(OutFile)) + setupFileLimits() + + # TODO provide option for fixing / ignoring permission errors + if not checkAndCreateDataDir(conf.dataDir): + # We are unable to access/create data folder or data folder's + # permissions are insecure. + quit QuitFailure + + let nimbus = NimbusNode( + ctx: newEthContext(), + beaconSyncRef: BeaconSyncRef.init cfgCB) + + nimbus.run(conf) + +# End diff --git a/tools/syncer/helpers/sync_ticker.nim b/tools/syncer/helpers/sync_ticker.nim new file mode 100644 index 0000000000..21b0250ecd --- /dev/null +++ b/tools/syncer/helpers/sync_ticker.nim @@ -0,0 +1,182 @@ +# Nimbus - Fetch account and storage states from peers efficiently +# +# Copyright (c) 2021-2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +# http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +{.push raises: [].} + +import + std/strutils, + pkg/[chronos, chronicles, eth/common, stint, stew/interval_set], + ../../../execution_chain/sync/beacon/worker/[blocks, headers, worker_desc] + +logScope: + topics = "beacon ticker" + +type + TickerStats = object + ## Full sync state (see `TickerFullStatsUpdater`) + base: BlockNumber + latest: BlockNumber + coupler: BlockNumber + dangling: BlockNumber + top: BlockNumber + head: BlockNumber + target: BlockNumber + activeOk: bool + + hdrUnprocTop: BlockNumber + nHdrUnprocessed: uint64 + nHdrUnprocFragm: int + nHdrStaged: int + hdrStagedTop: BlockNumber + + blkUnprocBottom: BlockNumber + nBlkUnprocessed: uint64 + nBlkUnprocFragm: int + nBlkStaged: int + blkStagedBottom: BlockNumber + + state: SyncState + nBuddies: int + + TickerRef* = ref object of RootRef + ## Ticker descriptor object + started: Moment + visited: Moment + lastStats: TickerStats + +# ------------------------------------------------------------------------------ +# Private functions: printing ticker messages +# ------------------------------------------------------------------------------ + +const + tickerLogInterval = chronos.seconds(2) + tickerLogSuppressMax = chronos.seconds(100) + +proc updater(ctx: BeaconCtxRef): TickerStats = + ## Legacy stuff, will be probably be superseded by `metrics` + TickerStats( + base: ctx.chain.baseNumber, + latest: ctx.chain.latestNumber, + coupler: ctx.headersUnprocTotalBottom(), + dangling: ctx.hdrCache.antecedent.number, + top: ctx.subState.top, + head: ctx.subState.head, + target: ctx.hdrCache.latestConsHeadNumber, + activeOk: ctx.pool.lastState != idle, + + nHdrStaged: ctx.headersStagedQueueLen(), + hdrStagedTop: ctx.headersStagedQueueTopKey(), + hdrUnprocTop: ctx.headersUnprocTotalTop(), + nHdrUnprocessed: ctx.headersUnprocTotal(), + nHdrUnprocFragm: ctx.hdr.unprocessed.chunks, + + nBlkStaged: ctx.blocksStagedQueueLen(), + blkStagedBottom: ctx.blocksStagedQueueBottomKey(), + blkUnprocBottom: ctx.blocksUnprocTotalBottom(), + nBlkUnprocessed: ctx.blocksUnprocTotal(), + nBlkUnprocFragm: ctx.blk.unprocessed.chunks, + + state: ctx.pool.lastState, + nBuddies: ctx.pool.nBuddies) + +proc tickerLogger(t: TickerRef; ctx: BeaconCtxRef) = + let + data = ctx.updater() + now = Moment.now() + + if now <= t.visited + tickerLogInterval: + return + + if data != t.lastStats or + tickerLogSuppressMax < (now - t.visited): + let + B = if data.base == data.latest: "L" else: data.base.bnStr + L = if data.latest == data.coupler: "C" else: data.latest.bnStr + I = if data.top == 0: "n/a" else : data.top.bnStr + C = if data.coupler == data.dangling: "D" + elif data.coupler < high(int64).uint64: data.coupler.bnStr + else: "n/a" + D = if data.dangling == data.head: "H" else: data.dangling.bnStr + H = if data.head == data.target: "T" + elif data.activeOk: data.head.bnStr + else: "?" & $data.head + T = if data.activeOk: data.target.bnStr else: "?" & $data.target + + hS = if data.nHdrStaged == 0: "n/a" + else: data.hdrStagedTop.bnStr & "[" & $data.nHdrStaged & "]" + hU = if data.nHdrUnprocFragm == 0 and data.nHdrUnprocessed == 0: "n/a" + elif data.hdrUnprocTop == 0: + "(" & data.nHdrUnprocessed.toSI & "," & + $data.nHdrUnprocFragm & ")" + else: data.hdrUnprocTop.bnStr & "(" & + data.nHdrUnprocessed.toSI & "," & $data.nHdrUnprocFragm & ")" + hQ = if hS == "n/a": hU + elif hU == "n/a": hS + else: hS & "<-" & hU + + bS = if data.nBlkStaged == 0: "n/a" + else: data.blkStagedBottom.bnStr & "[" & $data.nBlkStaged & "]" + bU = if data.nBlkUnprocFragm == 0 and data.nBlkUnprocessed == 0: "n/a" + elif data.blkUnprocBottom == high(BlockNumber): + "(" & data.nBlkUnprocessed.toSI & "," & + $data.nBlkUnprocFragm & ")" + else: data.blkUnprocBottom.bnStr & "(" & + data.nBlkUnprocessed.toSI & "," & $data.nBlkUnprocFragm & ")" + bQ = if bS == "n/a": bU + elif bU == "n/a": bS + else: bS & "<-" & bU + + st = case data.state + of idle: "0" + of headers: "h" + of headersCancel: "x" + of headersFinish: "f" + of blocks: "b" + of blocksCancel: "x" + of blocksFinish: "f" + + nP = data.nBuddies + + # With `int64`, there are more than 29*10^10 years range for seconds + up = (now - t.started).seconds.uint64.toSI + mem = getTotalMem().uint.toSI + + t.lastStats = data + t.visited = now + + case data.state + of idle: + debug "Sync state idle", up, nP, B, L, + D, H, T, hQ, bQ, + mem + + of headers, headersCancel, headersFinish: + debug "Sync state headers", up, nP, st, B, L, + C, D, H, T, hQ, + mem + + of blocks, blocksCancel, blocksFinish: + debug "Sync state blocks", up, nP, st, B, L, + D, I, H, T, bQ, + mem + +# ------------------------------------------------------------------------------ +# Public function +# ------------------------------------------------------------------------------ + +proc syncTicker*(): BackgroundTicker = + let desc = TickerRef(started: Moment.now()) + return proc(ctx: BeaconCtxRef) = + desc.tickerLogger(ctx) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/syncer_test_client_trace.nim b/tools/syncer/syncer_test_client_trace.nim new file mode 100644 index 0000000000..b63b6d3113 --- /dev/null +++ b/tools/syncer/syncer_test_client_trace.nim @@ -0,0 +1,155 @@ +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + std/[cmdline, os, strutils], + pkg/[chronicles, results], + ../../execution_chain/config, + ../../execution_chain/sync/beacon, + ./helpers/[nimbus_el_wrapper, sync_ticker], + ./trace/trace_setup + +type + ArgsDigest = tuple + elArgs: seq[string] # split command line: left to "--" marker + fileName: string # capture file name + nSessions: int # capture modifier argument + nPeersMin: int # ditto + syncTicker: bool # .. + +let + cmdName = getAppFilename().extractFilename() + +# ------------------------------------------------------------------------------ +# Private helpers, command line parsing tools +# ------------------------------------------------------------------------------ + +proc argsCheck(q: seq[string]): seq[string] = + if q.len == 0 or + q[0] == "-h" or + q[0] == "--help": + echo "", + "Usage: ", cmdName, + " [.. --] [..]\n", + " Capture file:\n", + " Run a trace session and store captured states in the\n", + " argument.\n", + " Attributes:\n", + " nSessions=[0-9]+ Run a trace for this many sessions (i.e. from\n", + " activation to suspension). If set to 0, the\n", + " is ignored and will not be written.\n", + " However, other modifiers still have effcet.\n", + " nPeersMin=[0-9]+ Minimal number of peers needed for activating\n", + " the first syncer session.\n", + " syncTicker Log sync state regularly.\n" + quit(QuitFailure) + return q + +proc argsError(s: string) = + echo "*** ", cmdName, ": ", s, "\n" + discard argsCheck(@["-h"]) # usage & quit + +# ------------- + +proc parseCmdLine(): ArgsDigest = + ## Parse command line: + ## :: + ## [.. --] [nSessions=[0-9]+] .. + ## + var exArgs: seq[string] + + # Split command line by "--" into `exArgs[]` and `elArgs[]` + let args = commandLineParams().argsCheck() + for n in 0 ..< args.len: + if args[n] == "--": + if 0 < n: + result.elArgs = args[0 .. n-1] + if n < args.len: + exArgs = args[n+1 .. ^1].argsCheck() + break + + # Case: no delimiter "--" given + if exArgs.len == 0 and result.elArgs.len == 0: + exArgs = args + + result.fileName = exArgs[0] + result.nSessions = -1 + result.nPeersMin = -1 + for n in 1 ..< exArgs.len: + let w = exArgs[n].split('=',2) + + block: + # nSessions=[0-9]+ + const token = "nSessions" + if toLowerAscii(w[0]) == toLowerAscii(token): + if w.len < 2: + argsError("Sub-argument incomplete: " & token & "=[0-9]+") + try: + result.nSessions = int(w[1].parseBiggestUInt) + except ValueError as e: + argsError("Sub-argument value error: " & token & "=[0-9]+" & + ", error=" & e.msg) + continue + + block: + # nPeersMin=[0-9]+ + const token = "nPeersMin" + if toLowerAscii(w[0]) == toLowerAscii(token): + if w.len < 2: + argsError("Sub-argument incomplete: " & token & "=[0-9]+") + try: + result.nPeersMin = int(w[1].parseBiggestUInt) + except ValueError as e: + argsError("Sub-argument value error: " & token & "=[0-9]+" & + ", error=" & e.msg) + continue + + block: + # syncTicker + const token = "syncTicker" + if toLowerAscii(w[0]) == toLowerAscii(token): + if 1 < w.len: + argsError("Sub-argument has no value: " & token) + result.syncTicker = true + continue + + argsError("Sub-argument unknown: " & exArgs[n]) + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = + return proc(desc: BeaconSyncRef) = + if args.syncTicker: + desc.ctx.pool.ticker = syncTicker() + if 1 < args.nPeersMin: + desc.ctx.pool.minInitBuddies = args.nPeersMin + if args.nSessions == 0: + return + desc.ctx.traceSetup( + fileName = args.fileName, + nSessions = max(0, args.nSessions)).isOkOr: + fatal "Cannot set up trace handlers", error + quit(QuitFailure) + +# ------------------------------------------------------------------------------ +# Main +# ------------------------------------------------------------------------------ + +# Pre-parse command line +let argsDigest = parseCmdLine() + +# Early plausibility check +if argsDigest.fileName.fileExists: + argsError("Must not overwrite file: \"" & argsDigest.fileName & "\"") + +# Processing left part command line arguments +let conf = makeConfig(cmdLine = argsDigest.elArgs) + +# Run execution client +conf.runNimbusExeClient(argsDigest.beaconSyncConfig) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/syncer_test_client_trace.nim.cfg b/tools/syncer/syncer_test_client_trace.nim.cfg new file mode 100644 index 0000000000..cb44702917 --- /dev/null +++ b/tools/syncer/syncer_test_client_trace.nim.cfg @@ -0,0 +1,5 @@ +-d:"chronicles_sinks=textlines[stderr]" +-d:"chronicles_runtime_filtering=on" +-d:"chronicles_line_numbers=0" +-d:"chronicles_thread_ids=no" +-d:"chronicles_log_level=TRACE" diff --git a/tools/syncer/trace/README.md b/tools/syncer/trace/README.md new file mode 100644 index 0000000000..061aef127e --- /dev/null +++ b/tools/syncer/trace/README.md @@ -0,0 +1,46 @@ +Beacon sync tracer +================== + +For the *nimbus_execution_client* binary, data from a syncer sessions can +be captured into a file **(capture)** along with system state information +via + + ./build/syncer_test_client_trace ... -- (capture) + +where **...** stands for all other options that might be useful for running +an execution layer session. + +The capture file **(capture)** will hold enough data for replaying the +*nimbus_execution_client* session(s). + +With the command line option *\-\-debug-beacon-sync-trace-file=***(capture)** +for the *nimbus_execution_client* binary, data from the syncer sessions will +be dumped into the argument file **(capture)** along with system state +information. + +The capture file **(capture)** will hold enough data for replaying the +*nimbus_execution_client* session(s). + +By default, the captured syncer session starts with the first syncer activation +(when *Activating syncer* is logged) and ends when the syncer is suspended +(when *Suspending syncer* is logged.) + +The trace file **(capture)** is organised as an ASCII text file, each line +consists of a data capture record. The line format is + + + +where the ** is a single alphanumeric letter, and ** +is a base64 representation of an rlp-encoded data capture structure. + +By nature of the base64 representation, the size of the trace data is about +four times the data capture which leads to huge files, e.g. some 30GiB for the +last 120k blocks synchronised on *mainnet*. + +The file with the captured data may be gzipped after the dump finished which +reduces ths size roughly to 1/3. So altogether in its gzipped form, the size +of the gzipped trace file is about 4/3 of the capured data (mainly downloaded +block headers and bodies.) + +The captured data might be further processed (e.g. inspection or replay) in +its gzipped form. From 609c0bf166664e98028036d3bc0e024669ac2948 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Fri, 6 Jun 2025 12:29:34 +0100 Subject: [PATCH 04/18] Provide capture inspection framework, part of replay details Currently only for dumping capture data as logs --- tools/syncer/replay/replay_desc.nim | 93 +++++ tools/syncer/replay/replay_reader.nim | 76 ++++ .../replay/replay_reader/reader_desc.nim | 41 ++ .../replay/replay_reader/reader_gunzip.nim | 352 ++++++++++++++++++ .../replay/replay_reader/reader_helpers.nim | 73 ++++ .../replay/replay_reader/reader_init.nim | 142 +++++++ .../replay/replay_reader/reader_reclog.nim | 349 +++++++++++++++++ .../replay/replay_reader/reader_unpack.nim | 180 +++++++++ 8 files changed, 1306 insertions(+) create mode 100644 tools/syncer/replay/replay_desc.nim create mode 100644 tools/syncer/replay/replay_reader.nim create mode 100644 tools/syncer/replay/replay_reader/reader_desc.nim create mode 100644 tools/syncer/replay/replay_reader/reader_gunzip.nim create mode 100644 tools/syncer/replay/replay_reader/reader_helpers.nim create mode 100644 tools/syncer/replay/replay_reader/reader_init.nim create mode 100644 tools/syncer/replay/replay_reader/reader_reclog.nim create mode 100644 tools/syncer/replay/replay_reader/reader_unpack.nim diff --git a/tools/syncer/replay/replay_desc.nim b/tools/syncer/replay/replay_desc.nim new file mode 100644 index 0000000000..1b561c05e6 --- /dev/null +++ b/tools/syncer/replay/replay_desc.nim @@ -0,0 +1,93 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + ../trace/trace_desc, + ./replay_reader/reader_desc + +export + reader_desc, + trace_desc + +const + ReplaySetupID* = 2 ## Phase 1 layout ID, prepare + ReplayRunnerID* = 20 ## Phase 2 layout ID, full execution + +type + ReplayPayloadRef* = ref object of RootRef + ## Decoded payload base record + recType*: TraceRecType + + ReplayVersionInfo* = ref object of ReplayPayloadRef + data*: TraceVersionInfo + + # ------------- + + ReplaySyncActvFailed* = ref object of ReplayPayloadRef + data*: TraceSyncActvFailed + + ReplaySyncActivated* = ref object of ReplayPayloadRef + data*: TraceSyncActivated + + ReplaySyncHibernated* = ref object of ReplayPayloadRef + data*: TraceSyncHibernated + + # ------------- + + ReplaySchedDaemonBegin* = ref object of ReplayPayloadRef + data*: TraceSchedDaemonBegin + + ReplaySchedDaemonEnd* = ref object of ReplayPayloadRef + data*: TraceSchedDaemonEnd + + ReplaySchedStart* = ref object of ReplayPayloadRef + data*: TraceSchedStart + + ReplaySchedStop* = ref object of ReplayPayloadRef + data*: TraceSchedStop + + ReplaySchedPool* = ref object of ReplayPayloadRef + data*: TraceSchedPool + + ReplaySchedPeerBegin* = ref object of ReplayPayloadRef + data*: TraceSchedPeerBegin + + ReplaySchedPeerEnd* = ref object of ReplayPayloadRef + data*: TraceSchedPeerEnd + + # ------------- + + ReplayFetchHeaders* = ref object of ReplayPayloadRef + data*: TraceFetchHeaders + + ReplaySyncHeaders* = ref object of ReplayPayloadRef + data*: TraceSyncHeaders + + + ReplayFetchBodies* = ref object of ReplayPayloadRef + data*: TraceFetchBodies + + ReplaySyncBodies* = ref object of ReplayPayloadRef + data*: TraceSyncBodies + + + ReplayImportBlock* = ref object of ReplayPayloadRef + data*: TraceImportBlock + + ReplaySyncBlock* = ref object of ReplayPayloadRef + data*: TraceSyncBlock + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader.nim b/tools/syncer/replay/replay_reader.nim new file mode 100644 index 0000000000..b5b7f2354e --- /dev/null +++ b/tools/syncer/replay/replay_reader.nim @@ -0,0 +1,76 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + std/[net, syncio], + ./replay_reader/[reader_init, reader_unpack, reader_reclog], + ./replay_desc + +export + ReplayReaderRef, + reader_init + +type + StopFn* = proc(): bool {.gcsafe, raises: [].} + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc nextRecord*(rp: ReplayReaderRef): ReplayPayloadRef = + ## Retrieve the next record from the capture + while true: + var line = rp.readLine(rp).valueOr: + return ReplayPayloadRef(nil) + if 0 < line.len and line[0] != '#': + return line.unpack() + +proc captureLog*( + rp: ReplayReaderRef; + prt: ReplayRecLogPrintFn; + stop: StopFn; + ) = + ## Cycle through capture records from `rp` and feed them to the + ## argument `prt()`. + var n = 0 + while not stop(): + let w = rp.nextRecord() + if w.isNil and rp.atEnd(rp): + break + n.inc + prt w.recLogToStrList(n) + prt n.recLogToStrEnd() + +proc captureLog*( + rp: ReplayReaderRef; + stop: StopFn; + ) = + ## Pretty print linewise records from the capture `rp`. + rp.captureLog(stdout.recLogPrint(), stop) + +# ------------------------------------------------------------------------------ +# Public iterators +# ------------------------------------------------------------------------------ + +iterator records*(rp: ReplayReaderRef): ReplayPayloadRef = + ## Iterate over all capture records + while true: + let record = rp.nextRecord() + if record.isNil and rp.atEnd(rp): + break + yield record + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_desc.nim b/tools/syncer/replay/replay_reader/reader_desc.nim new file mode 100644 index 0000000000..3c48011fee --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_desc.nim @@ -0,0 +1,41 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + std/streams, + pkg/results, + ./reader_gunzip + +type + ReplayRecLogPrintFn* = proc(s: seq[string]) {.gcsafe, raises: [].} + ## Print output (e.g. used in `lineLog()`) for logger + + ReplayReadLineFn* = + proc(rp: ReplayReaderRef): Opt[string] {.gcsafe, raises: [].} + ## Reader filter, e.g. for zipped data + + ReplayAtEndFn* = + proc(rp: ReplayReaderRef): bool {.gcsafe, raises: [].} + ## Indicated end of stream + + ReplayReaderRef* = ref object + ## Reader descriptor + inStream*: Stream ## Dump file for ethxx data packets + gzFilter*: GUnzipRef ## Apply GUnzip filter to stream + readLine*: ReplayReadLineFn ## Reader function + atEnd*: ReplayAtEndFn ## EOF indicator + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_gunzip.nim b/tools/syncer/replay/replay_reader/reader_gunzip.nim new file mode 100644 index 0000000000..d1c4cd6dad --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_gunzip.nim @@ -0,0 +1,352 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +# http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except +# according to those terms. + +## Incremental unzip based on `Stream` input (derived from +## `test/replay/unzip.nim`.) + +{.push raises:[].} + +import + std/[os, streams, strutils], + pkg/[chronicles, results, zlib] + +logScope: + topics = "replay gunzip" + +const + DontQuit = low(int) + ## To be used with `onCloseException()` + + ReadBufLen = 2048 + ## Size of data chunks to be read from stream. + +type + GUnzipStatus* = tuple + zError: ZError + info: string + + GUnzipRef* = ref object + mz: ZStream ## Gzip sub-system + nextInBuf: array[4096,char] ## Input buffer for gzip `mz.next_in` + nextOutBuf: array[2048,char] ## Output buffer for gzip `mz.next_out` + + inStream: Stream ## Input stream + inName: string ## Registered gzip file name (if any) + outDoneOK: bool ## Gzip/inflate stream end indicator + + lnCache: string ## Input line buffer, used by `nextLine` + lnError: GUnzipStatus ## Last error cache for line iterator + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +template onException( + info: static[string]; + quitCode: static[int]; + code: untyped) = + try: + code + except CatchableError as e: + const blurb = info & "Gunzip exception" + when quitCode == DontQuit: + error blurb, error=($e.name), msg=e.msg + else: + fatal blurb & " -- STOP", error=($e.name), msg=e.msg + quit(quitCode) + +proc extractLine(gz: GUnzipRef; start: int): Opt[string] = + ## Extract the first string from line buffer. Any newline characters at + ## the line end will be stripped. The argument `start` is the position + ## where to start searching for the `\n` character. + ## + # Find `\n` in the buffer if there is any + if gz.lnCache.len <= start: + return err() + var nlPos = gz.lnCache.find(char('\n'), start) + if nlPos < 0: + return err() + + # Assemble return value + var line = gz.lnCache.toOpenArray(0,nlPos-1).substr() + line.stripLineEnd + + # Update line cache + gz.lnCache = if gz.lnCache.len <= nlPos + 1: "" + else: gz.lnCache.toOpenArray(nlPos+1, gz.lnCache.len-1).substr() + + # Done + ok(move line) + +# ------------------------------------------------------------------------------ +# Private inflate function +# ------------------------------------------------------------------------------ + +proc loadInput(gz: GUnzipRef; data: openArray[char]): string = + ## Fill input chache for `explode()` and return the overflow. + ## + # Gzip input buffer general layout + # :: + # | <---------------- nextInBuf.len -------------------------> | + # |--------------------+--------------------+------------------| + # | <--- total_in ---> | <--- avail_in ---> | <--- unused ---> | + # | | + # nextInBuf next_in + # + # to be initialised as + # :: + # | <---------------- nextInBuf.len -------------------------> | + # |--------------------------+---------------------------------| + # | <------ avail_in ------> | <----------- unused ----------> | + # | + # nextInBuf + # next_in + # + var buffer = newSeqUninit[char](gz.mz.avail_in.int + data.len) + + # Collect remaining data first + if 0 < gz.mz.avail_in: + (addr buffer[0]).copyMem(gz.mz.next_in, gz.mz.avail_in) + + # Append new data + (addr buffer[gz.mz.avail_in]).copyMem(addr data[0], data.len) + + # Realign gzip input buffer and fill as much as possible from `buffer[]` + gz.mz.next_in = cast[ptr uint8](addr gz.nextInBuf[0]) + gz.mz.total_in = 0 + + if gz.nextInBuf.len < buffer.len: + # The `buffer[]` does not fully fit into `nextInBuf[]`. + (addr gz.nextInBuf).copyMem(addr buffer[0], gz.nextInBuf.len) + gz.mz.avail_in = gz.nextInBuf.len.cuint + # Return overflow + return buffer.toOpenArray(gz.nextInBuf.len, buffer.len-1).substr() + + (addr gz.nextInBuf).copyMem(addr buffer[0], buffer.len) + gz.mz.avail_in = buffer.len.cuint + return "" + + +proc explodeImpl(gz: GUnzipRef; overflow: var string): Result[string,ZError] = + ## Implement `explode()` processing. + ## + if gz.outDoneOK: + return err(Z_STREAM_END) + + var + outData = "" + zRes = Z_STREAM_END + + while not gz.outDoneOK and 0 < gz.mz.avail_in: + gz.mz.next_out = cast[ptr uint8](addr gz.nextOutBuf[0]) + gz.mz.avail_out = gz.nextOutBuf.len.cuint + gz.mz.total_out = 0 + + # Save input state to compare with, below + let availIn = gz.mz.avail_in + + # Deflate current block `next_in[]` => `next_out[]` + zRes = gz.mz.inflate(Z_SYNC_FLUSH) + if zRes == Z_STREAM_END: + gz.outDoneOK = true + zRes = gz.mz.inflateEnd() + # Dont't stop here, `outData` needs to be assigned + if zRes != Z_OK: + break + + # Append processed data + if 0 < gz.mz.total_out: + outData &= gz.nextOutBuf.toOpenArray(0, gz.mz.total_out-1).substr() + + if gz.outDoneOK: + break + + if gz.mz.avail_in < availIn: + # Re-load overflow + if 0 < overflow.len: + overflow = gz.loadInput overflow.toOpenArray(0, overflow.len-1) + + elif gz.mz.avail_out == gz.nextOutBuf.len.cuint: + # Stop unless state change + zRes = Z_BUF_ERROR + break + + if zRes != Z_OK: + return err(zRes) + + ok(outData) + + +proc explode(gz: GUnzipRef; data: openArray[char]): Result[string,ZError] = + ## Inflate the `data[]` argument together with the rest from the previous + ## inflation action and returns the inflated value (and possibly the input + ## buffer overflow.) + ## + var overflow = gz.loadInput data + gz.explodeImpl(overflow) + +proc explode(gz: GUnzipRef): Result[string,ZError] = + ## Variant of `explode()` which clears the rest of the input buffer. + ## + var overflow = "" + gz.explodeImpl(overflow) + +# ------------------------------------------------------------------------------ +# Public +# ------------------------------------------------------------------------------ + +proc init*(T: type GUnzipRef; inStream: Stream): Result[T,GUnzipStatus] = + ## Set up gUnzip filter and prepare for deflating. + ## + const info = "GUnzipRef.init(): " + var chunk: array[ReadBufLen,char] + + # Read header buffer from stream + var chunkLen: int + info.onException(DontQuit): + chunkLen = inStream.readData(addr chunk, chunk.len) + + # Parse GZIP header (RFC 1952) + if chunkLen < 18: + return err((Z_STREAM_ERROR, "Stream too short")) + if (chunk[0].ord != 0x1f or # magic number + chunk[1].ord != 0x8b or # magic number + chunk[2].ord != 0x08) or # deflate + (chunk[3].ord and 0xf7) != 0: # unsupported flags + return err((Z_STREAM_ERROR, "Wrong magic or flags")) + + # Set start of payload + var + pylStart = 10 + inName = "" + if (chunk[3].ord and 8) == 8: # FNAME + var endPos = chunk.toOpenArray(pylStart, chunkLen-1).find char(0) + if endPos < 0: + return err((Z_STREAM_ERROR, "Advertised but missing file name")) + endPos += pylStart # need absolute position in `chunk[]` + inName = chunk.toOpenArray(pylStart, endPos-1).substr() + pylStart = endPos + 1 + + # Initialise descriptor + let gz = GUnzipRef( + inStream: inStream, + inName: inName) + + # Initialise `zlib` and return + let gRc = gz.mz.inflateInit2(Z_RAW_DEFLATE) + if gRc != Z_OK: + return err((gRc,"Zlib init error")) + + # Store unused buffer data for the first read + gz.mz.avail_in = (chunk.len - pylStart).cuint + (addr gz.nextInBuf).copyMem(addr chunk[pylStart], gz.mz.avail_in.int) + gz.mz.next_in = cast[ptr uint8](addr gz.nextInBuf[0]) + gz.mz.total_in = 0 # i.e. left aligned data + + ok(gz) + +proc name*(gz: GUnzipRef): string = + ## Getter: returns registered name (if any) + gz.inName + + +proc nextChunk*(gz: GUnzipRef): Result[string,GUnzipStatus] = + ## Fetch next unzipped data chunk, return and empty string if input + ## is exhausted. + ## + const info = "nextChunk(GUnzipRef): " + + if gz.outDoneOK: + return err((Z_STREAM_END,"")) + + var + chunk: array[ReadBufLen,char] + chunkLen = 0 + data = "" + + info.onException(DontQuit): + chunkLen = gz.inStream.readData(addr chunk, chunk.len) + + if 0 < chunkLen: + data = gz.explode(chunk.toOpenArray(0, chunkLen-1)).valueOr: + return err((error,"Decoding error")) + else: + var atEnd = false + info.onException(DontQuit): + atEnd = gz.inStream.atEnd() + if atEnd: + data = gz.explode().valueOr: + return err((error,"Decoding error")) + else: + return err((Z_STREAM_ERROR, "Stream too short")) + + return ok(move data) + + +proc nextLine*(gz: GUnzipRef): Result[string,GUnzipStatus] = + ## If the gzip stream is expected to contain text data only it can be + ## retrieved line wise. The line string returned has the EOL characters + ## stripped. + ## + ## If all lines are exhausted, the error code `Z_STREAM_END` is returned. + ## + # Check whether there is a full line in the buffer, already + gz.extractLine(0).isErrOr: + return ok(value) + + # Load next chunk(s) into line cache and (try to) extract a complete line. + while not gz.outDoneOK: + let chunk = gz.nextChunk().valueOr: + if gz.outDoneOK: + break + return err(error) + + # Append data chunk to line cache and (try to) extract a line. + let inLen = gz.lnCache.len + gz.lnCache &= chunk + gz.extractLine(inLen).isErrOr: + return ok(value) + # continue + + # Last line (may be partial) + if 0 < gz.lnCache.len: + var line = gz.lnCache + line.stripLineEnd + gz.lnCache = "" + return ok(move line) + + err((Z_STREAM_END,"")) + + +proc atEnd*(gz: GUnzipRef): bool = + ## Returns `true` if data are exhausted. + gz.outDoneOK and gz.lnCache.len == 0 + + +iterator line*(gz: GUnzipRef): string = + ## Iterate over `nextLine()` until the input stream is exhausted. + gz.lnError = (Z_OK, "") + while true: + var ln = gz.nextLine().valueOr: + gz.lnError = error + break + yield ln + +func lineStatus*(gz: GUnzipRef): GUnzipStatus = + ## Error (or no-error) status after the `line()` iterator has terminated. + gz.lnError + +func lineStatusOk*(gz: GUnzipRef): bool = + ## Returns `true` if the `line()` iterator has terminated without error. + gz.lnError[0] in {Z_OK, Z_STREAM_END} + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_helpers.nim b/tools/syncer/replay/replay_reader/reader_helpers.nim new file mode 100644 index 0000000000..842b0127f4 --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_helpers.nim @@ -0,0 +1,73 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay helpers + +{.push raises:[].} + +import + std/strutils, + pkg/[chronos, eth/common], + ../../trace/trace_setup/setup_helpers as trace_helpers, + ../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers + +export + trace_helpers.idStr, + trace_helpers.short, + worker_helpers + +# ------------------------------------------------------------------------------ +# Public helpers +# ------------------------------------------------------------------------------ + +func ageStr*(w: chronos.Duration): string = + var + res = newStringOfCap(32) + nsLeft = w.nanoseconds() + + # Inspired by `chronos/timer.toString()` + template f( + pfxChr: static[char]; + pfxLen: static[int]; + ela: static[chronos.Duration]; + sep: static[string]; + ) = + let n = uint64(nsLeft div ela.nanoseconds()) + when pfxLen == 0: + let s = if 0 < n: $n else: "" + else: + let s = $n + when 0 < pfxLen: + res.add pfxChr.repeat(max(0, pfxLen - s.len)) + res.add s + when pfxLen == 0: + if 0 < n: res.add sep + else: + res.add sep + nsLeft = nsLeft mod ela.nanoseconds() + + f(' ', 0, chronos.Day, "d ") + f('0', 2, chronos.Hour, ":") + f('0', 2, chronos.Minute, ":") + f('0', 2, chronos.Second, ".") + f('0', 3, chronos.Millisecond, ".") + f('0', 3, chronos.Microsecond, "") + + res + +func toUpperFirst*(w: string): string = + if 1 < w.len: + $w[0].toUpperAscii & w.substr(1) + else: + w + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_init.nim b/tools/syncer/replay/replay_reader/reader_init.nim new file mode 100644 index 0000000000..c655172f96 --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_init.nim @@ -0,0 +1,142 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + std/[endians, os, streams, strutils], + pkg/[chronicles, eth/common], + ../replay_desc, + ./reader_gunzip + +logScope: + topics = "replay reader" + +type + FileSignature = enum + Unknown = 0 + Plain + Gzip + +const + DontQuit = low(int) + ## To be used with `onCloseException()` + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + + +template onException( + info: static[string]; + quitCode: static[int]; + code: untyped) = + try: + code + except CatchableError as e: + const blurb = info & "Replay stream exception" + when quitCode == DontQuit: + error blurb, error=($e.name), msg=e.msg + else: + fatal blurb & " -- STOP", error=($e.name), msg=e.msg + quit(quitCode) + +proc getFileSignature(strm: Stream): (FileSignature,uint16) = + const info = "getSignature(): " + var u16: uint16 + info.onException(QuitFailure): + let v16 = strm.peekUint16() + (addr u16).bigEndian16(addr v16) + + # Gzip signature + if u16 == 0x1f8b'u16: + return (Gzip,u16) + + # Ascii signature: /[0-9A-Z] / + let (c0, c1) = (char(u16 shr 8), char(u16.uint8)) + if (c0.isDigit or c0.isUpperAscii or c0 == '#') and (c1 in {' ','\r','\n'}): + return (Plain,u16) + + (Unknown,u16) + +# ------------------------------------------------------------------------------ +# Private record reader functions +# ------------------------------------------------------------------------------ + +proc plainReadLine(rp: ReplayReaderRef): Opt[string] = + const info = "plainReadLine(ReplayRef): " + info.onException(DontQuit): + if not rp.inStream.atEnd(): + return ok(rp.inStream.readLine) + err() + +proc plainAtEnd(rp: ReplayReaderRef): bool = + const info = "plainAtEnd(ReplayRef): " + info.onException(DontQuit): + return rp.inStream.atEnd() + true + +proc gUnzipReadLine(rp: ReplayReaderRef): Opt[string] = + const info = "gzipReadLine(ReplayRef): " + var ln = rp.gzFilter.nextLine().valueOr: + if not rp.gzFilter.lineStatusOk(): + let err = rp.gzFilter.lineStatus() + info info & "GUnzip filter error", zError=err.zError, info=err.info + discard + return err() + ok(move ln) + +proc gUnzipAtEnd(rp: ReplayReaderRef): bool = + rp.gzFilter.atEnd() + +# ------------------------------------------------------------------------------ +# Public constructor(s) +# ------------------------------------------------------------------------------ + +proc init*(T: type ReplayReaderRef; strm: Stream): T = + const info = "ReplayRef.init(): " + + if strm.isNil: + fatal info & "Cannot use nil stream for reading -- STOP" + quit(QuitFailure) + + let + (sig, u16) = strm.getFileSignature() # Check file encoding + rp = T(inStream: strm) # Set up descriptor + + # Set up line reader, probably with gunzip/deflate filter + case sig: + of Plain: + rp.readLine = plainReadLine + rp.atEnd = plainAtEnd + of Gzip: + rp.gzFilter = GUnzipRef.init(strm).valueOr: + fatal info & "Cannot assign gunzip reader -- STOP" + quit(QuitFailure) + rp.readLine = gUnzipReadLine + rp.atEnd = gUnzipAtEnd + of Unknown: + fatal info & "Unsupported file encoding -- STOP", + fileSignature=("0x" & $u16.toHex(4)) + quit(QuitFailure) + + rp + + +proc destroy*(rp: ReplayReaderRef) = + const info = "destroy(ReplayRef): " + info.onException(DontQuit): + rp.inStream.flush() + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim new file mode 100644 index 0000000000..282d66c871 --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -0,0 +1,349 @@ + +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + std/[net, strformat, strutils, syncio], + pkg/[chronicles, chronos, eth/common, eth/rlp], + ../../../../execution_chain/utils/prettify, + ../replay_desc, + ./reader_helpers + +logScope: + topics = "replay reader" + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +proc addX( + q: var seq[string]; + info: static[string]; + lnr: int; + base: TraceRecBase; + ) = + ## Output header + q.add base.time.ageStr() + q.add info + if 0 < lnr and base.serial != lnr.uint: + q.add $base.serial & "!" & $lnr + else: + q.add $base.serial + + if 0 < base.frameID: + q.add base.frameID.idStr + else: + q.add "*" + q.add $base.nPeers + q.add ($base.syncState).toUpperFirst() + q.add ($base.chainMode).toUpperFirst() + + q.add base.baseNum.bnStr() + q.add base.latestNum.bnStr() + + if base.chainMode in {collecting,ready,orphan}: + q.add base.antecedent.bnStr() + else: + q.add "*" + + q.add (if (base.stateAvail and 1) != 0: $base.peerCtrl else: "*") + q.add (if (base.stateAvail and 2) != 0: "peerID=" & base.peerID.short() + else: "*") + + if 0 < base.hdrUnprChunks: + q.add "uHdr=" & $base.hdrUnprLen & "/" & + $base.hdrUnprChunks & "/" & + $base.hdrUnprLastLen & ":" & + $base.hdrUnprLast.bnStr + + if 0 < base.blkUnprChunks: + q.add "uBlk=" & $base.blkUnprLen & "/" & + $base.blkUnprChunks & "/" & + $base.blkUnprLeast.bnStr & ":" & + $base.blkUnprLeastLen + + if (base.stateAvail and 12) != 0 and + (0 < base.nHdrErrors or 0 < base.nBlkErrors): + q.add "nErr=(" & $base.nHdrErrors & "," & $base.nBlkErrors & ")" + + if (base.stateAvail and 16) != 0: + q.add "slowPeer=" & base.slowPeer.short() + +# ------------------------------------------------------------------------------ +# Private record handlers +# ------------------------------------------------------------------------------ + +func toStrOops(n: int): seq[string] = + @["?", $n] + +# ----------- + +func toStrSeq(n: int; w: TraceVersionInfo): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("=Version", n, w) + res.add "version=" & $w.version + res.add "network=" & $w.networkId + res.add "base=" & w.baseNum.bnStr + res.add "latest=" & w.latestNum.bnStr + res + +# ----------- + +func toStrSeq(n: int; w: TraceSyncActvFailed): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("=ActvFailed", n, w) + res.add "base=" & w.baseNum.bnStr + res.add "latest=" & w.latestNum.bnStr + res + +func toStrSeq(n: int; w: TraceSyncActivated): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=Activated", n, w) + res.add "head=" & w.head.bnStr + res.add "finHash=" & w.finHash.short + res.add "base=" & w.baseNum.bnStr + res.add "latest=" & w.latestNum.bnStr + res + +func toStrSeq(n: int; w: TraceSyncHibernated): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("=Suspended", n, w) + res.add "base=" & w.baseNum.bnStr + res.add "latest=" & w.latestNum.bnStr + res + +# ----------- + +func toStrSeq(n: int; w: TraceSchedDaemonBegin): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("+Daemon", n, w) + res + +func toStrSeq(n: int; w: TraceSchedDaemonEnd): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("-Daemon", n, w) + res + +func toStrSeq(n: int; w: TraceSchedStart): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=StartPeer", n, w) + res.add "peer=" & $w.peerIP & ":" & $w.peerPort + if not w.accept: + res.add "rejected" + res + +func toStrSeq(n: int; w: TraceSchedStop): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=StopPeer", n, w) + res.add "peer=" & $w.peerIP & ":" & $w.peerPort + res + +func toStrSeq(n: int; w: TraceSchedPool): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=Pool", n, w) + res.add "peer=" & $w.peerIP & ":" & $w.peerPort + res.add "last=" & $w.last + res.add "laps=" & $w.laps + res.add "stop=" & $w.stop + res + +func toStrSeq(n: int; w: TraceSchedPeerBegin): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("+Peer", n, w) + res.add "peer=" & $w.peerIP & ":" & $w.peerPort + res + +func toStrSeq(n: int; w: TraceSchedPeerEnd): seq[string] = + var res = newSeqOfCap[string](15) + res.addX("-Peer", n, w) + res + +# ----------- + +func toStrSeq(n: int; w: TraceFetchHeaders): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=HeadersFetch", n, w) + let + rLen = w.req.maxResults + rRev = if w.req.reverse: "rev" else: "" + if w.req.startBlock.isHash: + res.add "req=" & w.req.startBlock.hash.short & "[" & $rLen & "]" & rRev + else: + res.add "req=" & w.req.startBlock.number.bnStr & "[" & $rLen & "]" & rRev + if 0 < w.req.skip: + res.add "skip=" & $w.req.skip + if (w.fieldAvail and 1) != 0: + res.add "res=[" & $w.fetched.packet.headers.len & "]" + res.add "ela=" & w.fetched.elapsed.toStr + if (w.fieldAvail and 2) != 0: + if w.error.excp.ord == 0: + res.add "failed" + else: + res.add "excp=" & ($w.error.excp).substr(1) + if w.error.msg.len != 0: + res.add "error=" & w.error.name & "(" & w.error.msg & ")" + res.add "ela=" & w.error.elapsed.toStr + res + +func toStrSeq(n: int; w: TraceSyncHeaders): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=HeadersSync", n, w) + res + + +func toStrSeq(n: int; w: TraceFetchBodies): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=BodiesFetch", n, w) + res.add "req=" & w.ivReq.bnStr & "[" & $w.req.blockHashes.len & "]" + if (w.fieldAvail and 1) != 0: + res.add "res=[" & $w.fetched.packet.bodies.len & "]" + res.add "size=" & w.fetched.packet.bodies.getEncodedLength.uint64.toSI + res.add "ela=" & w.fetched.elapsed.toStr + if (w.fieldAvail and 2) != 0: + if w.error.excp.ord == 0: + res.add "failed" + else: + res.add "excp=" & ($w.error.excp).substr(1) + if w.error.msg.len != 0: + res.add "error=" & w.error.name & "(" & w.error.msg & ")" + res.add "ela=" & w.error.elapsed.toStr + res + +func toStrSeq(n: int; w: TraceSyncBodies): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=BodiesSync", n, w) + res + + +func toStrSeq(n: int; w: TraceImportBlock): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=BlockImport", n, w) + res.add "block=" & w.ethBlock.bnStr + res.add "size=" & w.ethBlock.getEncodedLength.uint64.toSI + res.add "effPeerID=" & w.effPeerID.short + if (w.fieldAvail and 1) != 0: + res.add "ela=" & w.elapsed.toStr + if (w.fieldAvail and 2) != 0: + if w.error.excp.ord == 0: + res.add "failed" + else: + res.add "excp=" & ($w.error.excp).substr(1) + if w.error.msg.len != 0: + res.add "error=" & w.error.name & "(" & w.error.msg & ")" + res.add "ela=" & w.error.elapsed.toStr + res + +func toStrSeq(n: int; w: TraceSyncBlock): seq[string] = + var res = newSeqOfCap[string](20) + res.addX("=BlockSync", n, w) + res + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc recLogPrint*(fh: File): ReplayRecLogPrintFn = + ## The function provides an example for a call back pretty printer + ## for `lineLog()`. + return proc(w: seq[string]) = + try: + block doFields: + if w.len <= 9: + fh.write w.join(" ") + break doFields + + # at least 9 fields + fh.write "" & + &"{w[0]:>18} {w[1]:<13} {w[2]:>6} " & + &"{w[3]:>5} {w[4]:>2} {w[5]:<13} " & + &"{w[6]:<10} {w[7]:>10} {w[8]:>10} " & + &"{w[9]:>10}" + + if w.len <= 11: + if w.len == 11: + fh.write " " + fh.write w[10] + break doFields + + # at least 12 fields + if w.len <= 12: + fh.write &" {w[10]:<10} " + fh.write w[11] + break doFields + + # more than 12 fields + fh.write &" {w[10]:<10} {w[11]:<15}" + + # at least 13 fields + fh.write " " + fh.write w[12 ..< w.len].join(" ") + + fh.write "\n" + except IOError as e: + warn "lineLogPrint(): Exception while writing to file", + name=($e.name), msg=e.msg + +# ----------- + +func recLogToStrEnd*(n: int): seq[string] = + @[".", $n] + +proc recLogToStrList*(pyl: ReplayPayloadRef; lnr = 0): seq[string] = + case pyl.recType: + of TrtOops: + lnr.toStrOops() + + of TrtVersionInfo: + lnr.toStrSeq(pyl.ReplayVersionInfo.data) + + of TrtSyncActvFailed: + lnr.toStrSeq(pyl.ReplaySyncActvFailed.data) + of TrtSyncActivated: + lnr.toStrSeq(pyl.ReplaySyncActivated.data) + of TrtSyncHibernated: + lnr.toStrSeq(pyl.ReplaySyncHibernated.data) + + of TrtSchedDaemonBegin: + lnr.toStrSeq(pyl.ReplaySchedDaemonBegin.data) + of TrtSchedDaemonEnd: + lnr.toStrSeq(pyl.ReplaySchedDaemonEnd.data) + of TrtSchedStart: + lnr.toStrSeq(pyl.ReplaySchedStart.data) + of TrtSchedStop: + lnr.toStrSeq(pyl.ReplaySchedStop.data) + of TrtSchedPool: + lnr.toStrSeq(pyl.ReplaySchedPool.data) + of TrtSchedPeerBegin: + lnr.toStrSeq(pyl.ReplaySchedPeerBegin.data) + of TrtSchedPeerEnd: + lnr.toStrSeq(pyl.ReplaySchedPeerEnd.data) + + of TrtFetchHeaders: + lnr.toStrSeq(pyl.ReplayFetchHeaders.data) + of TrtSyncHeaders: + lnr.toStrSeq(pyl.ReplaySyncHeaders.data) + + of TrtFetchBodies: + lnr.toStrSeq(pyl.ReplayFetchBodies.data) + of TrtSyncBodies: + lnr.toStrSeq(pyl.ReplaySyncBodies.data) + + of TrtImportBlock: + lnr.toStrSeq(pyl.ReplayImportBlock.data) + of TrtSyncBlock: + lnr.toStrSeq(pyl.ReplaySyncBlock.data) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_unpack.nim b/tools/syncer/replay/replay_reader/reader_unpack.nim new file mode 100644 index 0000000000..653aeccb94 --- /dev/null +++ b/tools/syncer/replay/replay_reader/reader_unpack.nim @@ -0,0 +1,180 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay environment + +{.push raises:[].} + +import + std/[net, os, streams, strutils], + pkg/[chronicles, chronos, eth/common, stew/base64, stew/byteutils], + ./reader_gunzip, + ../replay_desc + +logScope: + topics = "replay reader" + +const + DontQuit = low(int) + ## To be used with `onCloseException()` + +# ------------------------------------------------------------------------------ +# Private mixin helpers for RLP decoder +# ------------------------------------------------------------------------------ + +proc read(rlp: var Rlp; T: type Hash): T {.raises:[RlpError].} = + when sizeof(T) != sizeof(uint): + # `castToUnsigned()` is defined in `std/private/bitops_utils` and + # included by `std/bitops` but not exported (as of nim 2.2.4) + {.error: "Expected that Hash is based on int".} + Hash(int(cast[int64](rlp.read(uint64)))) + +proc read(rlp: var Rlp; T: type chronos.Duration): T {.raises:[RlpError].} = + chronos.nanoseconds(cast[int64](rlp.read(uint64))) + +proc read(rlp: var Rlp; T: type Port): T {.raises:[RlpError].} = + Port(rlp.read(uint16)) + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +template onException( + info: static[string]; + quitCode: static[int]; + code: untyped) = + try: + code + except CatchableError as e: + const blurb = info & "Replay stream reader exception" + when quitCode == DontQuit: + error blurb, error=($e.name), msg=e.msg + else: + fatal blurb & " -- STOP", error=($e.name), msg=e.msg + quit(quitCode) + +proc init(T: type; blob: string; recType: static[TraceRecType]; U: type): T = + const info = "init(" & $recType & "): " + var rlpBlob: seq[byte] + info.onException(DontQuit): + rlpBlob = Base64.decode(blob) + return T( + recType: recType, + data: rlp.decode(rlpBlob, U)) + +# ------------------------------------------------------------------------------ +# Public record decoder functions +# ------------------------------------------------------------------------------ + +proc unpack*(line: string): ReplayPayloadRef = + if line.len < 3: + return ReplayPayloadRef(nil) + + var recType: TraceRecType + if line[0].isDigit: + let n = line[0].ord - '0'.ord + if high(TraceRecType).ord < n: + return ReplayPayloadRef(nil) + recType = TraceRecType(n) + + elif line[0].isUpperAscii: + let n = line[0].ord - 'A'.ord + 10 + if high(TraceRecType).ord < n: + return ReplayPayloadRef(nil) + recType = TraceRecType(n) + + else: + return ReplayPayloadRef(nil) + + let data = line.substr(2, line.len-1) + case recType: + of TrtOops: + return ReplayPayloadRef( + recType: TrtOops) + + of TrtVersionInfo: + return ReplayVersionInfo.init( + data, TrtVersionInfo, TraceVersionInfo) + + # ------------------ + + of TrtSyncActvFailed: + return ReplaySyncActvFailed.init( + data, TrtSyncActvFailed, TraceSyncActvFailed) + + of TrtSyncActivated: + return ReplaySyncActivated.init( + data, TrtSyncActivated, TraceSyncActivated) + + of TrtSyncHibernated: + return ReplaySyncHibernated.init( + data, TrtSyncHibernated, TraceSyncHibernated) + + # ------------------ + + of TrtSchedDaemonBegin: + return ReplaySchedDaemonBegin.init( + data, TrtSchedDaemonBegin, TraceSchedDaemonBegin) + + of TrtSchedDaemonEnd: + return ReplaySchedDaemonEnd.init( + data, TrtSchedDaemonEnd, TraceSchedDaemonEnd) + + of TrtSchedStart: + return ReplaySchedStart.init( + data, TrtSchedStart, TraceSchedStart) + + of TrtSchedStop: + return ReplaySchedStop.init( + data, TrtSchedStop, TraceSchedStop) + + of TrtSchedPool: + return ReplaySchedPool.init( + data, TrtSchedPool, TraceSchedPool) + + of TrtSchedPeerBegin: + return ReplaySchedPeerBegin.init( + data, TrtSchedPeerBegin, TraceSchedPeerBegin) + + of TrtSchedPeerEnd: + return ReplaySchedPeerEnd.init( + data, TrtSchedPeerEnd, TraceSchedPeerEnd) + + # ------------------ + + of TrtFetchHeaders: + return ReplayFetchHeaders.init( + data, TrtFetchHeaders, TraceFetchHeaders) + + of TrtSyncHeaders: + return ReplaySyncHeaders.init( + data, TrtSyncHeaders, TraceSyncHeaders) + + + of TrtFetchBodies: + return ReplayFetchBodies.init( + data, TrtFetchBodies, TraceFetchBodies) + + of TrtSyncBodies: + return ReplaySyncBodies.init( + data, TrtSyncBodies, TraceSyncBodies) + + + of TrtImportBlock: + return ReplayImportBlock.init( + data, TrtImportBlock, TraceImportBlock) + + of TrtSyncBlock: + return ReplaySyncBlock.init( + data, TrtSyncBlock, TraceSyncBlock) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ From 9005334d57cd966363b2279b3a518d164ad06fab Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Thu, 28 Aug 2025 17:11:54 +0100 Subject: [PATCH 05/18] Provide command line capture inspection tool --- Makefile | 2 +- tools/syncer/replay/README.md | 10 +++ .../replay/replay_reader/reader_init.nim | 1 - .../replay/replay_reader/reader_reclog.nim | 5 +- tools/syncer/syncer_test_client_inspect.nim | 68 +++++++++++++++++++ .../syncer/syncer_test_client_inspect.nim.cfg | 5 ++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 tools/syncer/replay/README.md create mode 100644 tools/syncer/syncer_test_client_inspect.nim create mode 100644 tools/syncer/syncer_test_client_inspect.nim.cfg diff --git a/Makefile b/Makefile index 07c605dc16..30e22bc618 100644 --- a/Makefile +++ b/Makefile @@ -389,7 +389,7 @@ txparse: | build deps # build syncer debugging and analysis tools SYNCER_TOOLS_DIR := tools/syncer -SYNCER_TOOLS := $(foreach name,trace,syncer_test_client_$(name)) +SYNCER_TOOLS := $(foreach name,trace inspect,syncer_test_client_$(name)) .PHONY: syncer-tools syncer-tools-clean $(SYNCER_TOOLS) syncer-tools: $(SYNCER_TOOLS) syncer-tools-clean: diff --git a/tools/syncer/replay/README.md b/tools/syncer/replay/README.md new file mode 100644 index 0000000000..ec9fd272ba --- /dev/null +++ b/tools/syncer/replay/README.md @@ -0,0 +1,10 @@ +Inspection of Capture Data And Replay +===================================== + +Inspection +---------- + +Given a (probably gzipped) capture file **(capture)** as a result of + tracing, its content can be visualised via + + ./build/syncer_test_client_inspect (capture) diff --git a/tools/syncer/replay/replay_reader/reader_init.nim b/tools/syncer/replay/replay_reader/reader_init.nim index c655172f96..9469676781 100644 --- a/tools/syncer/replay/replay_reader/reader_init.nim +++ b/tools/syncer/replay/replay_reader/reader_init.nim @@ -35,7 +35,6 @@ const # Private helpers # ------------------------------------------------------------------------------ - template onException( info: static[string]; quitCode: static[int]; diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim index 282d66c871..d2fc7d999b 100644 --- a/tools/syncer/replay/replay_reader/reader_reclog.nim +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -92,7 +92,10 @@ func toStrOops(n: int): seq[string] = func toStrSeq(n: int; w: TraceVersionInfo): seq[string] = var res = newSeqOfCap[string](15) res.addX("=Version", n, w) - res.add "version=" & $w.version + let moan = if w.version < TraceVersionID: "(<" & $TraceVersionID & ")" + elif TraceVersionID < w.version: "(>" & $TraceVersionID & ")" + else: "" + res.add "version=" & $w.version & moan res.add "network=" & $w.networkId res.add "base=" & w.baseNum.bnStr res.add "latest=" & w.latestNum.bnStr diff --git a/tools/syncer/syncer_test_client_inspect.nim b/tools/syncer/syncer_test_client_inspect.nim new file mode 100644 index 0000000000..7efe916944 --- /dev/null +++ b/tools/syncer/syncer_test_client_inspect.nim @@ -0,0 +1,68 @@ +# Nimbus +# Copyright (c) 2018-2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + std/[cmdline, os, streams, strutils], + pkg/chronicles, + pkg/beacon_chain/process_state, + ./replay/replay_reader + +let cmdName = getAppFilename().extractFilename() + +# ------------------------------------------------------------------------------ +# Private helpers, command line parsing tools +# ------------------------------------------------------------------------------ + +proc argsCheck(q: seq[string]): seq[string] = + if q.len == 0 or + q[0] == "-h" or + q[0] == "--help": + echo "", + "Usage: ", cmdName, " [--] \n", + " Capture file:\n", + " Read from argument and print its contents." + quit(QuitFailure) + q + +proc argsError(s: string) = + echo "*** ", cmdName, ": ", s, "\n" + discard argsCheck(@["-h"]) # usage & quit + +# ------------- + +proc parseCmdLine(): string = + var args = commandLineParams().argsCheck + if args[0] == "--": + if args.len == 1: + argsError("Missing capture file argument") + args = args[1 .. ^1] + if args.len == 0: + argsError("Missing capture file argiment") + if 1 < args.len: + argsError("Extra arguments: " & args[1 .. ^1].join(" ") & ".") + return args[0] + +# ------------------------------------------------------------------------------ +# Main +# ------------------------------------------------------------------------------ + +let name = parseCmdLine() +if not name.fileExists: + argsError("No such capture file: \"" & name & "\"") + +ProcessState.setupStopHandlers() +ProcessState.notifyRunning() + +let reader = ReplayReaderRef.init(name.newFileStream fmRead) +reader.captureLog(stop = proc: bool = + ProcessState.stopIt(notice("Terminating", reason = it))) + +quit(QuitSuccess) + +# End diff --git a/tools/syncer/syncer_test_client_inspect.nim.cfg b/tools/syncer/syncer_test_client_inspect.nim.cfg new file mode 100644 index 0000000000..cb44702917 --- /dev/null +++ b/tools/syncer/syncer_test_client_inspect.nim.cfg @@ -0,0 +1,5 @@ +-d:"chronicles_sinks=textlines[stderr]" +-d:"chronicles_runtime_filtering=on" +-d:"chronicles_line_numbers=0" +-d:"chronicles_thread_ids=no" +-d:"chronicles_log_level=TRACE" From 5446e81b01a9e969a3621744c4b56143bce57a86 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Tue, 2 Sep 2025 17:59:50 +0100 Subject: [PATCH 06/18] Update replay framework for full capture replay --- tools/syncer/replay/replay_desc.nim | 32 +- tools/syncer/replay/replay_runner.nim | 54 ++ .../replay/replay_runner/runner_desc.nim | 116 +++ .../replay/replay_runner/runner_dispatch.nim | 102 +++ .../runner_dispatch/dispatch_blocks.nim | 230 +++++ .../runner_dispatch/dispatch_headers.nim | 111 +++ .../runner_dispatch/dispatch_helpers.nim | 809 ++++++++++++++++++ .../runner_dispatch/dispatch_sched.nim | 209 +++++ .../runner_dispatch/dispatch_sync.nim | 91 ++ .../runner_dispatch/dispatch_version.nim | 76 ++ .../replay/replay_runner/runner_init.nim | 80 ++ tools/syncer/replay/replay_setup.nim | 195 +++++ 12 files changed, 2104 insertions(+), 1 deletion(-) create mode 100644 tools/syncer/replay/replay_runner.nim create mode 100644 tools/syncer/replay/replay_runner/runner_desc.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim create mode 100644 tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim create mode 100644 tools/syncer/replay/replay_runner/runner_init.nim create mode 100644 tools/syncer/replay/replay_setup.nim diff --git a/tools/syncer/replay/replay_desc.nim b/tools/syncer/replay/replay_desc.nim index 1b561c05e6..79cab15632 100644 --- a/tools/syncer/replay/replay_desc.nim +++ b/tools/syncer/replay/replay_desc.nim @@ -13,11 +13,15 @@ {.push raises:[].} import + std/streams, + pkg/chronos, ../trace/trace_desc, - ./replay_reader/reader_desc + ./replay_reader/reader_desc, + ./replay_runner/runner_desc export reader_desc, + runner_desc, trace_desc const @@ -25,6 +29,23 @@ const ReplayRunnerID* = 20 ## Phase 2 layout ID, full execution type + ReplayStopIfFn* = proc(): bool {.gcsafe, raises: [].} + ## Loop control directive for runner/dispatcher + + ReplayEndUpFn* = proc() {.gcsafe, raises: [].} + ## Terminator control directive for runner/dispatcher + + ReplayRef* = ref object of BeaconHandlersSyncRef + ## Overlay handlers extended by descriptor data for caching replay state + ctx*: BeaconCtxRef ## Parent context + captStrm*: Stream ## Input stream, capture file + fakeImport*: bool ## No database import if `true` + stopQuit*: bool ## Quit after replay + backup*: BeaconHandlersRef ## Can restore previous handlers + reader*: ReplayReaderRef ## Input records + runner*: ReplayRunnerRef ## Replay descriptor + + ReplayPayloadRef* = ref object of RootRef ## Decoded payload base record recType*: TraceRecType @@ -88,6 +109,15 @@ type ReplaySyncBlock* = ref object of ReplayPayloadRef data*: TraceSyncBlock +# ------------------------------------------------------------------------------ +# Public helpers +# ------------------------------------------------------------------------------ + +func replay*(ctx: BeaconCtxRef): ReplayRef = + ## Getter, get replay descriptor (if any) + if ctx.handler.version == ReplayRunnerID: + return ctx.handler.ReplayRef + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner.nim b/tools/syncer/replay/replay_runner.nim new file mode 100644 index 0000000000..08b761bb38 --- /dev/null +++ b/tools/syncer/replay/replay_runner.nim @@ -0,0 +1,54 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/chronos, + ./replay_runner/runner_dispatch, + ./[replay_desc, replay_reader] + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc runDispatcher*( + runner: ReplayRunnerRef; + reader: ReplayReaderRef; + stopIf: ReplayStopIfFn; + endUp: ReplayEndUpFn; + ) {.async: (raises: []).} = + block body: + for w in reader.records(): + # Dispatch next instruction record + await runner.dispatch(w) + + # Can continue? + if stopIf(): + break body + + # Wait for optional task switch + try: await sleepAsync replayWaitForCompletion + except CancelledError: break + + # Can continue? + if stopIf(): + break body + + # Finish + await runner.dispatchEnd() + + endUp() + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_desc.nim b/tools/syncer/replay/replay_runner/runner_desc.nim new file mode 100644 index 0000000000..51ebfb92b6 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_desc.nim @@ -0,0 +1,116 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner +## + +{.push raises:[].} + +import + std/tables, + pkg/chronos, + ../../../../execution_chain/networking/p2p, + ../../../../execution_chain/sync/wire_protocol, + ../../../../execution_chain/sync/beacon/worker/worker_desc, + ../../trace/trace_desc + +const + replayWaitForCompletion* = chronos.nanoseconds(100) + ## Wait for other pseudo/async thread to have completed something + + replayFailTimeout* = chronos.seconds(50) + ## Bail out after waiting this long for an event to happen. This + ## timeout should cover the maximum time needed to import a block. + + replayFailTmoMinLog* = chronos.milliseconds(1) + ## Log maximum elapsed time when it exceeds this threshold. + + replayWaitMuted* = chronos.milliseconds(200) + ## Some handlers are muted, but keep them in a waiting loop so + ## the system can terminate + +type + ReplayWaitError* = tuple + ## Capture exception or error context for waiting/polling instance + excp: BeaconErrorType + name: string + msg: string + + # --------- internal data message types --------- + + ReplayMsgRef* = ref object of RootRef + ## Sub task context ## Identifies captured environment + recType*: TraceRecType ## Sub-type selector + + ReplayFetchHeadersMsgRef* = ref object of ReplayMsgRef + ## Headers fetch data message + instr*: TraceFetchHeaders ## Full context/environment + + ReplaySyncHeadersMsgRef* = ref object of ReplayMsgRef + ## Headers fetch sync message + instr*: TraceSyncHeaders ## Full context/environment + + ReplayFetchBodiesMsgRef* = ref object of ReplayMsgRef + ## Bodies fetch data message + instr*: TraceFetchBodies ## Full context/environment + + ReplaySyncBodiesMsgRef* = ref object of ReplayMsgRef + ## Bodies fetch sync message + instr*: TraceSyncBodies ## Full context/environment + + ReplayImportBlockMsgRef* = ref object of ReplayMsgRef + ## Block import data message + instr*: TraceImportBlock ## Full context/environment + + ReplaySyncBlockMsgRef* = ref object of ReplayMsgRef + ## Block import sync message + instr*: TraceSyncBlock ## Full context/environment + + # --------- internal context types --------- + + ReplayBuddyRef* = ref object of BeaconBuddyRef + ## Replacement of `BeaconBuddyRef` in `runPeer()` and `runPool()` + isNew*: bool ## Set in `getOrNewPeer()` when created + run*: ReplayRunnerRef ## Back-reference for convenience + frameID*: uint64 ## Begin/end frame + message*: ReplayMsgRef ## Data message channel + + ReplayDaemonRef* = ref object + ## Daemeon job frame (similar to `ReplayBuddyRef`) + run*: ReplayRunnerRef ## Back-reference for convenience + frameID*: uint64 ## Begin/end frame + message*: ReplayMsgRef ## Data message channel + + # --------- + + ReplayEthState* = object + ## Some feake settings to pretent eth/xx compatibility + capa*: Dispatcher ## Cabability `eth68`, `eth69`, etc. + prots*: array[MAX_PROTOCOLS,RootRef] ## `capa` init flags, protocol states + + ReplayRunnerRef* = ref object + # Global state + ctx*: BeaconCtxRef ## Beacon syncer descriptor + worker*: BeaconHandlersRef ## Refers to original handlers table + ethState*: ReplayEthState ## For ethxx compatibility + stopRunner*: bool ## Shut down request + nSessions*: int ## Numer of sessions left + + # Local state + daemon*: ReplayDaemonRef ## Currently active daemon, or `nil` + peers*: Table[Hash,ReplayBuddyRef] ## Begin/End for base frames + nPeers*: uint ## Track active peer instances + failTmoMax*: chronos.Duration ## Keep track of largest timeout + + # Instruction handling + instrNumber*: uint ## Instruction counter + fakeImport*: bool ## No database import if `true` + +# End diff --git a/tools/syncer/replay/replay_runner/runner_dispatch.nim b/tools/syncer/replay/replay_runner/runner_dispatch.nim new file mode 100644 index 0000000000..ed58511bac --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch.nim @@ -0,0 +1,102 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/[chronicles, chronos], + ../../../../execution_chain/networking/p2p, + ../replay_desc, + ./runner_dispatch/[dispatch_blocks, dispatch_headers, dispatch_sched, + dispatch_sync, dispatch_version] + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +proc dispatch*( + run: ReplayRunnerRef; + pyl: ReplayPayloadRef; + ) {.async: (raises: []).} = + ## Execure next instruction + ## + run.instrNumber.inc + + trace "+dispatch()", n=run.instrNumber, recType=pyl.recType, + nBuddies=run.peers.len, nDaemons=(if run.daemon.isNil: 0 else: 1) + + case pyl.recType: + of TrtOops: + warn "dispatch(): Oops, unexpected void record", n=run.instrNumber + + of TrtVersionInfo: + run.versionInfoWorker(pyl.ReplayVersionInfo.data, "=Version") + + of TrtSyncActvFailed: + run.syncActvFailedWorker(pyl.ReplaySyncActvFailed.data, "=ActvFailed") + of TrtSyncActivated: + run.syncActivateWorker(pyl.ReplaySyncActivated.data, "=Activated") + of TrtSyncHibernated: + run.syncSuspendWorker(pyl.ReplaySyncHibernated.data, "=Suspended") + + # Simple scheduler single run (no begin/end) functions + of TrtSchedStart: + run.schedStartWorker(pyl.ReplaySchedStart.data, "=StartPeer") + of TrtSchedStop: + run.schedStopWorker(pyl.ReplaySchedStop.data, "=StopPeer") + of TrtSchedPool: + run.schedPoolWorker(pyl.ReplaySchedPool.data, "=Pool") + + # Workers, complex run in background + of TrtSchedDaemonBegin: + await run.schedDaemonBegin(pyl.ReplaySchedDaemonBegin.data, "+Daemon") + of TrtSchedDaemonEnd: + await run.schedDaemonEnd(pyl.ReplaySchedDaemonEnd.data, "-Daemon") + of TrtSchedPeerBegin: + await run.schedPeerBegin(pyl.ReplaySchedPeerBegin.data, "+Peer") + of TrtSchedPeerEnd: + await run.schedPeerEnd(pyl.ReplaySchedPeerEnd.data, "-Peer") + + # Leaf handlers providing input data to background tasks `runDaemon()` + # and/or `runPeer()`. + of TrtFetchHeaders: + await run.sendHeaders(pyl.ReplayFetchHeaders.data, "=HeadersFetch") + of TrtSyncHeaders: + await run.sendHeaders(pyl.ReplaySyncHeaders.data, "=HeadersSync") + + of TrtFetchBodies: + await run.sendBodies(pyl.ReplayFetchBodies.data, "=BodiesFetch") + of TrtSyncBodies: + await run.sendBodies(pyl.ReplaySyncBodies.data, "=BodiesSync") + + of TrtImportBlock: + await run.sendBlock(pyl.ReplayImportBlock.data, "=BlockImport") + of TrtSyncBlock: + await run.sendBlock(pyl.ReplaySyncBlock.data, "=BlockSync") + + trace "-dispatch()", n=run.instrNumber, recType=pyl.recType, + nBuddies=run.peers.len, nDaemons=(if run.daemon.isNil: 0 else: 1) + + +proc dispatchEnd*( + run: ReplayRunnerRef; + ) {.async: (raises: []).} = + # Finish + run.instrNumber.inc + info "End replay", n=run.instrNumber + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim new file mode 100644 index 0000000000..3c11c597bf --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim @@ -0,0 +1,230 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/[chronicles, chronos, eth/common, stew/interval_set], + ../../../../../execution_chain/sync/wire_protocol, + ../../replay_desc, + ./dispatch_helpers + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc toBnRange( + ctx: BeaconCtxRef; + lst: openArray[Hash32]; + info: static[string]; + ): BnRange = + ## Resolve block hashes as interval of block numbers + let rs = BnRangeSet.init() + for w in lst: + let h = ctx.hdrCache.get(w).valueOr: + raiseAssert info & ": Cannot resolve" & + ", hash=" & w.short + if rs.merge(h.number,h.number) != 1: + raiseAssert info & ": dulplicate hash" & + ", hash=" & w.short & ", number=" & h.bnStr + rs.ge().expect "valid BnRange" + +proc bnStr( + lst: openArray[Hash32]; + buddy: BeaconBuddyRef; + info: static[string]; + ): string = + buddy.ctx.toBnRange(lst, info).bnStr + +proc toStr(e: BeaconError; anyTime = false): string = + "(" & $e[0] & + "," & $e[1] & + "," & $e[2] & + "," & (if anyTime: "*" else: e[3].toStr) & + ")" + +# ---------------- + +func getResponse( + instr: TraceFetchBodies; + ): Result[FetchBodiesData,BeaconError] = + if (instr.fieldAvail and 1) != 0: + ok(instr.fetched) + else: + err(instr.error) + +func getResponse( + instr: TraceImportBlock; + ): Result[Duration,BeaconError] = + if (instr.fieldAvail and 1) != 0: + ok(instr.elapsed) + else: + err(instr.error) + +func getBeaconError(e: ReplayWaitError): BeaconError = + (e[0], e[1], e[2], Duration()) + +# ------------------------------------------------------------------------------ +# Public dispatcher handlers +# ------------------------------------------------------------------------------ + +proc fetchBodiesHandler*( + buddy: BeaconBuddyRef; + req: BlockBodiesRequest; + ): Future[Result[FetchBodiesData,BeaconError]] + {.async: (raises: []).} = + const info = "&fetchBodies" + let buddy = ReplayBuddyRef(buddy) + + var data: TraceFetchBodies + buddy.withInstr(typeof data, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + if req != instr.req: + raiseAssert info & ": arguments differ" & + ", serial=" & $instr.serial & + ", peer=" & $buddy.peer & + # ----- + ", nBlockHashes=" & $req.blockHashes.len & + ", expected=" & $instr.ivReq.len & + # ----- + ", blockHashes=" & req.blockHashes.bnStr(buddy, info) & + ", expected=" & instr.ivReq.bnStr + data = instr + + buddy.withInstr(TraceSyncBodies, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + discard # no-op, visual alignment + + return data.getResponse() + + +proc importBlockHandler*( + buddy: BeaconBuddyRef; + ethBlock: EthBlock; + effPeerID: Hash; + ): Future[Result[Duration,BeaconError]] + {.async: (raises: []).} = + const info = "&importBlock" + + let + buddy = ReplayBuddyRef(buddy) + n = buddy.iNum + peer = buddy.peerStr + peerID = buddy.peerIdStr + + var data: TraceImportBlock + buddy.withInstr(typeof data, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + + if effPeerID != instr.effPeerID: + raiseAssert info & ": eff. peer arguments differ" & + ", n=" & $n & + ", serial=" & $instr.serial & + ", peer=" & $peer & + ", peerID=" & $peerID & + ", ethBlock=" & ethBlock.bnStr & + # ----- + ", effPeerID=" & effPeerID.short & + ", expected=" & instr.effPeerID.short + + if ethBlock != instr.ethBlock: + raiseAssert info & ": block arguments differ" & + ", n=" & $n & + ", serial=" & $instr.serial & + ", peer=" & $peer & + ", peerID=" & $peerID & + ", effPeerID=" & effPeerID.short & + # ----- + ", ethBlock=" & ethBlock.bnStr & + ", expected=%" & instr.ethBlock.bnStr & + # ----- + ", ethBlock=%" & ethBlock.computeRlpHash.short & + ", expected=%" & instr.ethBlock.computeRlpHash.short + data = instr + + let + ctx = buddy.run.ctx + rpl = ctx.replay + if not rpl.runner.fakeImport: + let rc = await rpl.backup.importBlock(buddy, ethBlock, effPeerID) + if rc.isErr or (data.fieldAvail and 2) != 0: + const info = info & ": result values differ" + let serial = data.serial + if rc.isErr and (data.fieldAvail and 2) == 0: + warn info, n, serial, peer, peerID, + got="err" & rc.error.toStr, expected="ok" + elif rc.isOk and (data.fieldAvail and 2) != 0: + warn info, n, serial, peer, peerID, + got="ok", expected="err" & data.error.toStr(true) + elif rc.error.excp != data.error.excp or + rc.error.msg != data.error.msg: + warn info, n, serial, peer, peerID, + got="err" & rc.error.toStr, expected="err" & data.error.toStr(true) + + buddy.withInstr(TraceSyncBlock, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + discard # no-op, visual alignment + + return data.getResponse() + +# ------------------------------------------------------------------------------ +# Public functions, data feed +# ------------------------------------------------------------------------------ + +proc sendBodies*( + run: ReplayRunnerRef; + instr: TraceFetchBodies|TraceSyncBodies; + info: static[string]; + ) {.async: (raises: []).} = + ## Stage bodies request/response data + let buddy = run.getPeer(instr, info).valueOr: + raiseAssert info & ": getPeer() failed" & + ", n=" & $run.iNum & + ", serial=" & $instr.serial & + ", peerID=" & instr.peerID.short + discard buddy.pushInstr(instr, info) + +proc sendBlock*( + run: ReplayRunnerRef; + instr: TraceImportBlock|TraceSyncBlock; + info: static[string]; + ) {.async: (raises: []).} = + ## Stage block request/response data + if (instr.stateAvail and 2) != 0: + # So it was captured run from a sync peer + let buddy = run.getPeer(instr, info).valueOr: + raiseAssert info & ": getPeer() failed" & + ", n=" & $run.iNum & + ", serial=" & $instr.serial & + ", peerID=" & instr.peerID.short + discard buddy.pushInstr(instr, info) + + # Verify that the daemon is properly initialised + elif run.daemon.isNil: + raiseAssert info & ": system error (no daemon)" & + ", n=" & $run.iNum & + ", serial=" & $instr.serial & + ", peer=n/a" + + else: + discard run.daemon.pushInstr(instr, info) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim new file mode 100644 index 0000000000..e751de8789 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim @@ -0,0 +1,111 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/[chronicles, chronos, eth/common], + ../../../../../execution_chain/sync/wire_protocol, + ../../replay_desc, + ./dispatch_helpers + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc `==`(a,b: BlockHeadersRequest): bool = + if a.maxResults == b.maxResults and + a.skip == b.skip: + if a.startBlock.isHash: + if b.startBlock.isHash and + a.startBlock.hash == b.startBlock.hash: + return true + else: + if not b.startBlock.isHash and + a.startBlock.number == b.startBlock.number: + return true + +func getResponse( + instr: TraceFetchHeaders; + ): Result[FetchHeadersData,BeaconError] = + if (instr.fieldAvail and 1) != 0: + ok(instr.fetched) + else: + err(instr.error) + +func getBeaconError(e: ReplayWaitError): BeaconError = + (e[0], e[1], e[2], Duration()) + +# ------------------------------------------------------------------------------ +# Public dispatcher handlers +# ------------------------------------------------------------------------------ + +proc fetchHeadersHandler*( + buddy: BeaconBuddyRef; + req: BlockHeadersRequest; + ): Future[Result[FetchHeadersData,BeaconError]] + {.async: (raises: []).} = + ## Replacement for `getBlockHeaders()` handler. + const info = "&fetchHeaders" + let buddy = ReplayBuddyRef(buddy) + + var data: TraceFetchHeaders + buddy.withInstr(typeof data, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + if req != instr.req: + raiseAssert info & ": arguments differ" & + ", n=" & $buddy.iNum & + ", serial=" & $instr.serial & + ", frameID=" & instr.frameID.idStr & + ", peer=" & $buddy.peer & + # ----- + ", reverse=" & $req.reverse & + ", expected=" & $instr.req.reverse & + # ----- + ", reqStart=" & req.startBlock.toStr & + ", expected=" & instr.req.startBlock.toStr & + # ----- + ", reqLen=" & $req.maxResults & + ", expected=" & $instr.req.maxResults + data = instr + + buddy.withInstr(TraceSyncHeaders, info): + if not instr.isAvailable(): + return err(iError.getBeaconError()) # Shutdown? + discard # no-op, visual alignment + + return data.getResponse() + +# ------------------------------------------------------------------------------ +# Public functions, data feed +# ------------------------------------------------------------------------------ + +proc sendHeaders*( + run: ReplayRunnerRef; + instr: TraceFetchHeaders|TraceSyncHeaders; + info: static[string]; + ) {.async: (raises: []).} = + ## Stage headers request/response data + let buddy = run.getPeer(instr, info).valueOr: + raiseAssert info & ": getPeer() failed" & + ", n=" & $run.iNum & + ", serial=" & $instr.serial & + ", peerID=" & instr.peerID.short + discard buddy.pushInstr(instr, info) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim new file mode 100644 index 0000000000..f8aa5dde03 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim @@ -0,0 +1,809 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + std/[net, tables], + pkg/[chronos, chronicles, stew/interval_set], + ../../../../../execution_chain/networking/[p2p, p2p_peers], + ../../../../../execution_chain/sync/wire_protocol, + ../../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers, + ../../../trace/trace_setup/setup_helpers as trace_helpers, + ../../replay_desc + +export + trace_helpers.idStr, + trace_helpers.short, + worker_helpers + +logScope: + topics = "replay runner" + +type + ReplayWaitResult* = Result[void,ReplayWaitError] + + ReplayInstance = ReplayDaemonRef | ReplayBuddyRef + + SubInstrType = TraceFetchHeaders | TraceSyncHeaders | + TraceFetchBodies | TraceSyncBodies | + TraceImportBlock | TraceSyncBlock + + InstrType = TraceSchedDaemonBegin | TraceSchedDaemonEnd | + TraceSchedPeerBegin | TraceSchedPeerEnd | + SubInstrType + +# ------------------------------------------------------------------------------ +# Private helper(s) +# ------------------------------------------------------------------------------ + +template waitForConditionImpl( + run: ReplayRunnerRef; + info: static[string]; + cond: untyped; + ): ReplayWaitResult = + ## Async/template + ## + ## Wait until the condition `cond()` becomes`true`. If a `stopRunner` flag + ## is becomes `true`, this wait function function returns `err(..)`, and + ## `ok()` otherwise. + ## + var bodyRc = ReplayWaitResult.ok() + block body: + let + start = Moment.now() + n {.inject.} = run.instrNumber + var + tmoPending {.inject.} = false + count {.inject.} = 0 + + while true: + count.inc + + if run.stopRunner: + chronicles.info info & ": runner stopped", n + bodyRc = ReplayWaitResult.err((ENoException,"",info&": runner stopped")) + break body # no timeout logging + + if cond: + break + + if tmoPending: + error info & ": timeout -- STOP", n, elapsed=(Moment.now()-start).toStr + run.stopRunner = true + bodyRc = ReplayWaitResult.err((ECancelledError,"",info&": timeout")) + break + + try: + await sleepAsync replayWaitForCompletion + except CancelledError as e: + chronicles.info info & ": cancelled -- STOP", n + run.stopRunner = true + bodyRc = ReplayWaitResult.err((ECancelledError,$e.name,e.msg)) + break + + if replayFailTimeout < Moment.now() - start: + tmoPending = true + + # End `while()` + + # Log maximum waiting time + let ela = Moment.now() - start + if run.failTmoMax < ela: + if replayFailTmoMinLog <= ela: + debug info & ": max waiting time", n, + elaMax=ela.toStr, lastElaMax=run.failTmoMax.toStr + run.failTmoMax = ela + + # End block: `body` + + bodyRc # result + + +func syncedEnvCondImpl( + desc: ReplayInstance; + instr: InstrType; + info: static[string]; + ): bool = + ## Condition function for `waitForConditionImpl()` for synchronising state. + ## + let ctx = desc.run.ctx + + #if serial != run.instrNumber: + # return false + + if instr.hdrUnprChunks != ctx.hdr.unprocessed.chunks().uint: + return false + if 0 < instr.hdrUnprChunks: + if instr.hdrUnprLen != ctx.hdr.unprocessed.total(): + return false + let iv = ctx.hdr.unprocessed.le().expect "valid iv" + if instr.hdrUnprLast != iv.maxPt or + instr.hdrUnprLastLen != iv.len: + return false + if instr.antecedent != ctx.hdrCache.antecedent.number: + return false + + if instr.blkUnprChunks != ctx.blk.unprocessed.chunks().uint: + return false + if 0 < instr.blkUnprChunks: + if instr.blkUnprLen != ctx.blk.unprocessed.total(): + return false + let iv = ctx.blk.unprocessed.ge().expect "valid iv" + if instr.blkUnprLeast != iv.minPt or + instr.blkUnprLeastLen != iv.len: + return false + + return true + + +proc newPeerImpl( + run: ReplayRunnerRef; + instr: TraceSchedStart|TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; + info: static[string]; + ): ReplayBuddyRef = + ## Register a new peer. + ## + run.peers.withValue(instr.peerID, val): + warn info & ": peer exists already", n=run.instrNumber, + serial=instr.serial, peer=($val.peer) + val.isNew = false + return val[] + + var buddy = ReplayBuddyRef( + isNew: true, + run: run, + ctx: run.ctx, + only: BeaconBuddyData( + nRespErrors: (instr.nHdrErrors, + instr.nBlkErrors)), + peerID: instr.peerID, + peer: Peer( + dispatcher: run.ethState.capa, + peerStates: run.ethState.prots, + remote: Node( + node: ENode( + address: enode.Address( + ip: instr.peerIP, + tcpPort: instr.peerPort, + udpPort: instr.peerPort))))) + + run.peers[instr.peerID] = buddy + return (move buddy) + +# ------------------------------------------------------------------------------ +# Private functions, environment checkers +# ------------------------------------------------------------------------------ + +proc baseStatesDifferImpl( + desc: ReplayRunnerRef | ReplayInstance; + instr: TraceRecBase; + ignLatestNum: static[bool]; + info: static[string]; + ): bool = + when desc is ReplayRunnerRef: + let (run, peer) = (desc, "n/a") + when desc is ReplayDaemonRef: + let (run, peer) = (desc.run, "n/a") + when desc is ReplayBuddyRef: + let (run, peer) = (desc.run, desc.peer) + + let + ctx = run.ctx + n = run.instrNumber + serial = instr.serial + var + statesDiffer = false + + if serial != n: + statesDiffer = true + info info & ": serial numbers differ", n, peer, serial, expected=n + + if ctx.chain.baseNumber != instr.baseNum: + statesDiffer = true + info info & ": base blocks differ", n, serial, peer, + base=instr.baseNum.bnStr, expected=ctx.chain.baseNumber.bnStr + + when not ignLatestNum: + if ctx.chain.latestNumber != instr.latestNum: + statesDiffer = true + info info & ": latest blocks differ", n, serial, peer, + latest=instr.latestNum.bnStr, expected=ctx.chain.latestNumber.bnStr + + if ctx.pool.lastState != instr.syncState: + statesDiffer = true + info info & ": sync states differ", n, serial, peer, + state=ctx.pool.lastState, expected=instr.syncState + + if ctx.hdrCache.state != instr.chainMode: + statesDiffer = true + info info & ": header chain modes differ", n, serial, peer, + chainMode=ctx.hdrCache.state, expected=instr.chainMode + elif instr.chainMode in {collecting,ready,orphan} and + instr.antecedent != ctx.hdrCache.antecedent.number: + statesDiffer = true + info info & ": header chain antecedents differ", n, serial, peer, + antecedent=ctx.hdrCache.antecedent.bnStr, expected=instr.antecedent.bnStr + + if ctx.pool.nBuddies != instr.nPeers.int: + statesDiffer = true + info info & ": number of active peers differs", n, serial, peer, + nBuddies=ctx.pool.nBuddies, expected=instr.nPeers + + if ctx.poolMode != instr.poolMode: + statesDiffer = true + info info & ": pool modes/reorgs differ", n, serial, peer, + poolMode=ctx.poolMode, expected=instr.poolMode + + return statesDiffer + + +proc unprocListsDifferImpl( + desc: ReplayRunnerRef | ReplayInstance; + instr: TraceRecBase; + info: static[string]; + ): bool = + when desc is ReplayRunnerRef: + let (run, peer) = (desc, "n/a") + when desc is ReplayDaemonRef: + let (run, peer) = (desc.run, "n/a") + when desc is ReplayBuddyRef: + let (run, peer) = (desc.run, desc.peer) + + let + ctx = run.ctx + n = run.instrNumber + serial = instr.serial + var + statesDiffer = false + + # Unprocessed block numbers for header + if instr.hdrUnprChunks != ctx.hdr.unprocessed.chunks().uint: + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + listChunks=ctx.hdr.unprocessed.chunks(), expected=instr.hdrUnprChunks + if 0 < instr.hdrUnprChunks: + if instr.hdrUnprLen != ctx.hdr.unprocessed.total(): + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + listLen=ctx.hdr.unprocessed.total(), expected=instr.hdrUnprLen + let iv = ctx.hdr.unprocessed.le().expect "valid iv" + if instr.hdrUnprLastLen != iv.len: + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + lastIvLen=iv.len, expected=instr.hdrUnprLastLen + if instr.hdrUnprLast != iv.maxPt: + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + lastIvMax=iv.maxPt, expected=instr.hdrUnprLast + + # Unprocessed block numbers for blocks + if instr.blkUnprChunks != ctx.blk.unprocessed.chunks().uint: + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + listChunks=ctx.blk.unprocessed.chunks(), expected=instr.blkUnprChunks + if 0 < instr.blkUnprChunks: + if instr.blkUnprLen != ctx.blk.unprocessed.total(): + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + listLen=ctx.blk.unprocessed.total(), expected=instr.blkUnprLen + let iv = ctx.blk.unprocessed.ge().expect "valid iv" + if instr.blkUnprLeastLen != iv.len: + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + lastIvLen=iv.len, expected=instr.blkUnprLeastLen + if instr.blkUnprLeast != iv.minPt: + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + lastIvMax=iv.maxPt, expected=instr.blkUnprLeast + + return statesDiffer + + +proc peerStatesDifferImpl( + desc: ReplayBuddyRef; + instr: TraceRecBase; + info: static[string]; + ): bool = + let + peer = desc.peer + n = desc.run.instrNumber + serial = instr.serial + var + statesDiffer = false + + if desc.ctrl.state != instr.peerCtrl: + statesDiffer = true + info info & ": peer ctrl states differ", n, serial, peer, + ctrl=desc.ctrl.state, expected=instr.peerCtrl + + if instr.nHdrErrors != desc.only.nRespErrors.hdr: + statesDiffer = true + info info & ": peer header errors differ", n, serial, peer, + nHdrErrors=desc.only.nRespErrors.hdr, expected=instr.nHdrErrors + + if instr.nBlkErrors != desc.only.nRespErrors.blk: + statesDiffer = true + info info & ": peer body errors differ", n, serial, peer, + nBlkErrors=desc.only.nRespErrors.blk, expected=instr.nBlkErrors + + return statesDiffer + +# ------------------------------------------------------------------------------ +# Public functions +# ------------------------------------------------------------------------------ + +func iNum*(desc: ReplayInstance|ReplayRunnerRef|BeaconCtxRef): uint = + when desc is ReplayRunnerRef: + desc.instrNumber + elif desc is BeaconCtxRef: + desc.replay.runner.instrNumber + else: + desc.run.instrNumber + +func toStr*(w: BlockHashOrNumber): string = + if w.isHash: w.hash.short else: w.number.bnStr + +func peerStr*(desc: ReplayInstance): string = + when desc is ReplayBuddyRef: + $desc.peer + elif desc is ReplayDaemonRef: + "n/a" + +func peerIdStr*(desc: ReplayInstance): string = + when desc is ReplayBuddyRef: + desc.peerID.short + elif desc is ReplayDaemonRef: + "n/a" + +# ----------------- + +proc stopError*(run: ReplayRunnerRef; info: static[string]) = + error info & " -- STOP", n=run.instrNumber + run.stopRunner = false + +proc stopOk*(run: ReplayRunnerRef; info: static[string]) = + info info & " -- STOP", n=run.instrNumber + run.stopRunner = false + +# ----------------- + +proc checkSyncerState*( + desc: ReplayRunnerRef | ReplayInstance; + instr: TraceRecBase; + ignLatestNum: static[bool]; + info: static[string]; + ): bool + {.discardable.} = + ## Check syncer states against all captured state variables of the + ## `instr` argument. + var statesDiffer = false + + if desc.baseStatesDifferImpl(instr, ignLatestNum, info): + statesDiffer = true + + if desc.unprocListsDifferImpl(instr, info): + statesDiffer = true + + when desc is ReplayBuddyRef: + if desc.peerStatesDifferImpl(instr, info): + statesDiffer = true + + return statesDiffer + +proc checkSyncerState*( + desc: ReplayRunnerRef | ReplayInstance; + instr: TraceRecBase; + info: static[string]; + ): bool + {.discardable.} = + desc.checkSyncerState(instr, false, info) + +# ------------------------------------------------------------------------------ +# Public functions, peer/daemon descriptor management +# ------------------------------------------------------------------------------ + +proc getPeer*( + run: ReplayRunnerRef; + instr: TraceRecBase; + info: static[string]; + ): Opt[ReplayBuddyRef] = + ## Get peer from peers table (if any) + run.peers.withValue(instr.peerID, buddy): + return ok(buddy[]) + + debug info & ": no peer", n=run.iNum, serial=instr.serial, + peerID=instr.peerID.short + return err() + + +proc newPeer*( + run: ReplayRunnerRef; + instr: TraceSchedStart; + info: static[string]; + ): ReplayBuddyRef = + ## Register a new peer. + ## + return run.newPeerImpl(instr, info) + + +proc getOrNewPeerFrame*( + run: ReplayRunnerRef; + instr: TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; + info: static[string]; + ): ReplayBuddyRef = + ## Get an existing one or register a new peer and set up `stage[0]`. + ## + var buddy: ReplayBuddyRef + run.peers.withValue(instr.peerID, val): + buddy = val[] + buddy.isNew = false + do: + buddy = run.newPeerImpl(instr, info) + + if buddy.frameID == 0: + buddy.frameID = instr.frameID + elif buddy.frameID != instr.frameID: + error info & ": frame unexpected", n=buddy.iNum, serial=instr.serial, + frameID=buddy.frameID.idStr, expected=instr.frameID.idStr + return move(buddy) + + +proc delPeer*( + buddy: ReplayBuddyRef; + info: static[string]; + ) = + ## Delete peer ID from registry and return the environment for the + ## deleted peer ID. + ## + let run = buddy.run + if run.peers.hasKey(buddy.peerID): + run.peers.del buddy.peerID + else: + trace info & ": stale peer ignored", n=buddy.iNum, + peer=($buddy.peer), peerID=buddy.peerID.short + +# ----------------- + +proc getDaemon*( + run: ReplayRunnerRef; + info: static[string]; + ): Opt[ReplayDaemonRef] = + ## Similar to `getPeer()` for daemon + if not run.daemon.isNil: + return ok(run.daemon) + + warn info & ": no daemon", n=run.instrNumber + return err() + + +proc newDaemonFrame*( + run: ReplayRunnerRef; + instr: TraceSchedDaemonBegin; + info: static[string]; + ): Opt[ReplayDaemonRef] = + ## Similar to `getOrNewPeerFrame()` for daemon. + if run.daemon.isNil: + run.daemon = ReplayDaemonRef( + run: run, + frameID: instr.frameID) + return ok(run.daemon) + + warn info & ": daemon already registered", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr + return err() + + +proc delDaemon*( + daemon: ReplayDaemonRef; + info: static[string]; + ) = + ## Similar to `delPeer()` for daemon + let run = daemon.run + if run.daemon.isNil: + trace info & ": stale daemon ignored", n=run.instrNumber + else: + run.daemon = ReplayDaemonRef(nil) + +# ------------------------------------------------------------------------------ +# Public functions, process/handler synchronisation +# ------------------------------------------------------------------------------ + +proc waitForSyncedEnv*( + desc: ReplayInstance; + instr: InstrType; + info: static[string]; + ): Future[ReplayWaitResult] + {.async: (raises: []).} = + ## .. + ## + when desc is ReplayBuddyRef: + # The scheduler (see `sync_sched.nim`) might have disconnected the peer + # already as is captured in the instruction environment. This does not + # apply to `zombie` settings which will be done by the application. + if instr.peerCtrl == Stopped and not desc.ctrl.stopped: + desc.ctrl.stopped = true + + let + serial {.inject,used.} = instr.serial + peer {.inject,used.} = desc.peerStr + peerID {.inject,used.} = desc.peerIdStr + + trace info & ": process to be synced", n=desc.iNum, serial, peer, peerID + + let rc = desc.run.waitForConditionImpl(info): + if tmoPending: + debug info & ": syncing", n=desc.iNum, serial, peer, count + desc.checkSyncerState(instr, info & ", syncing") + desc.syncedEnvCondImpl(instr, info) # cond result + + if rc.isErr(): + # Shutdown? + trace info & ": process sync error", n=desc.iNum, + serial, peer, peerID, name=rc.error.name, msg=rc.error.msg + return err(rc.error) + + trace info & ": process synced ok", n=desc.iNum, serial, peer, peerID + desc.checkSyncerState(instr, ignLatestNum=true, info) # relaxed check + + return ok() + +# ------------------ + +proc processFinished*( + desc: ReplayInstance; + instr: TraceSchedDaemonBegin|TraceSchedPeerBegin; + info: static[string]; + ) = + ## Register that the process has finished + ## + # Verify that sub-processes did not change the environment + doAssert desc.frameID == instr.frameID + + # Mark the pocess `done` + desc.frameID = 0 + + trace info & ": terminating", n=desc.iNum, + serial=instr.serial, frameID=instr.frameID.idStr, peer=desc.peerStr + + +template whenProcessFinished*( + desc: ReplayInstance; + instr: TraceSchedDaemonEnd|TraceSchedPeerEnd; + info: static[string]; + ): ReplayWaitResult = + ## Async/template + ## + ## Execude the argument `code` when the process related to the `instr` + ## argument flag has finished. The variables and functions available for + ## `code` are: + ## * `error` -- error data, initialised if `instr.isAvailable()` is `false` + ## + var bodyRc = ReplayWaitResult.ok() + block body: + let + peer {.inject,used.} = desc.peerStr + peerID {.inject,used.} = desc.peerIdStr + serial {.inject,used.} = instr.serial + + if desc.frameID != 0: + doAssert desc.frameID == instr.frameID + + trace info & ": wait for terminated", n=desc.iNum, serial, + frameID=instr.frameID.idStr, peer, peerID + + bodyRc = desc.run.waitForConditionImpl(info): + if tmoPending: + debug info & ": wait for terminated", n, serial, peer, count + desc.frameID == 0 + + if bodyRc.isErr: + break body + + trace info & ": terminated OK", n=desc.iNum, serial, + frameID=instr.frameID.idStr, peer + desc.checkSyncerState(instr, ignLatestNum=true, info) # relaxed check + + # Synchronise against captured environment + bodyRc = desc.run.waitForConditionImpl(info): + if tmoPending: + debug info & ": syncing", n=desc.iNum, serial, peer, peerID, count + desc.checkSyncerState(instr, info & ", syncing") + desc.syncedEnvCondImpl(instr, info) # cond result + + if bodyRc.isErr: + break body + + trace info & ": finished", n=desc.iNum, serial, + frameID=instr.frameID.idStr, peer, peerID + # End body + + bodyRc # result + +# ------------------ + +template pushInstr*( + desc: ReplayInstance; + instr: SubInstrType; + info: static[string]; + ): ReplayWaitResult = + ## Async/template + ## + ## Stage session data, then wait for the background process to consume the + ## session data using `withInstr()`. + ## + var bodyRc = ReplayWaitResult.ok() + block: + when instr is TraceFetchHeaders: + type T = ReplayFetchHeadersMsgRef + const dataType {.inject.} = TrtFetchHeaders + elif instr is TraceSyncHeaders: + type T = ReplaySyncHeadersMsgRef + const dataType {.inject.} = TrtSyncHeaders + + elif instr is TraceFetchBodies: + type T = ReplayFetchBodiesMsgRef + const dataType {.inject.} = TrtFetchBodies + elif instr is TraceSyncBodies: + type T = ReplaySyncBodiesMsgRef + const dataType {.inject.} = TrtSyncBodies + + elif instr is TraceImportBlock: + type T = ReplayImportBlockMsgRef + const dataType {.inject.} = TrtImportBlock + elif instr is TraceSyncBlock: + type T = ReplaySyncBlockMsgRef + const dataType {.inject.} = TrtSyncBlock + + # Verify that the stage is based on a proper environment + doAssert desc.frameID != 0 # this is not `instr.frameID` + + let + peer {.inject,used.} = desc.peerStr + peerID {.inject,used.} = desc.peerIdStr + serial {.inject.} = instr.serial + + doAssert serial == desc.iNum + doAssert desc.message.isNil + + # Stage/push session data + desc.message = T( + recType: dataType, + instr: instr) + + block body: + # Wait for sync # FIXME, really needed? + bodyRc = desc.run.waitForConditionImpl(info): # FIXME, really needed? + if tmoPending: + debug info & ": syncing", n, serial, peer, peerID, dataType, count + desc.checkSyncerState(instr, info & ", syncing") + desc.syncedEnvCondImpl(instr, info) # cond result + + if bodyRc.isErr(): + break body + + doAssert serial == desc.iNum + + trace info & ": sent data", n=desc.iNum, serial, peer, peerID, dataType + + # Wait for message to be swallowed + bodyRc = desc.run.waitForConditionImpl(info): + if tmoPending: + debug info & ": wait ackn", n, serial, peer, peerID, dataType, count + desc.message.isNil # cond result + + if bodyRc.isErr(): + break body + # End body + + trace info & ": done", n=desc.iNum, serial, peer, peerID, dataType + doAssert desc.iNum == serial + + bodyRc # result + + +template withInstr*( + desc: ReplayInstance; + I: type SubInstrType; + info: static[string]; + code: untyped; + ) = + ## Async/template + ## + ## Execude the argument `code` with the data sent by a feeder. The variables + ## and functions available for `code` are: + ## * `instr` -- instruction data, available if `instr.isAvailable()` is `true` + ## * `iError` -- error data, initialised if `instr.isAvailable()` is `false` + ## + block: + when I is TraceFetchHeaders: + const dataType {.inject.} = TrtFetchHeaders + type M = ReplayFetchHeadersMsgRef + const ignLatestNum = false + elif I is TraceSyncHeaders: + const dataType {.inject.} = TrtSyncHeaders + type M = ReplaySyncHeadersMsgRef + const ignLatestNum = false + + elif I is TraceFetchBodies: + const dataType {.inject.} = TrtFetchBodies + type M = ReplayFetchBodiesMsgRef + const ignLatestNum = true # relax, de-noise + elif I is TraceSyncBodies: + const dataType {.inject.} = TrtSyncBodies + type M = ReplaySyncBodiesMsgRef + const ignLatestNum = true # relax, de-noise + + elif I is TraceImportBlock: + const dataType {.inject.} = TrtImportBlock + type M = ReplayImportBlockMsgRef + const ignLatestNum = true # relax, de-noise + elif I is TraceSyncBlock: + const dataType {.inject.} = TrtSyncBlock + type M = ReplaySyncBlockMsgRef + const ignLatestNum = false + + let + run = desc.run + peer {.inject,used.} = desc.peerStr + peerID {.inject,used.} = desc.peerIdStr + + trace info & ": get data", n=desc.iNum, serial="n/a", + frameID="n/a", peer, dataType + + # Reset flag and wait for staged data to disappear from stack + let rc = run.waitForConditionImpl(info): + if tmoPending: + debug info & ": expecting data", n, serial="n/a", frameID="n/a", + peer, peerID, dataType, count + not desc.message.isNil # cond result + + var + iError {.inject.}: ReplayWaitError + instr {.inject.}: I + + if rc.isOk(): + instr = M(desc.message).instr + doAssert desc.message.recType == dataType + doAssert instr.serial == desc.iNum + + when desc is ReplayBuddyRef: + # The scheduler (see `sync_sched.nim`) might have disconnected the + # peer already which would be captured in the instruction environment. + # This does not apply to `zombie` settings which will be handled by + # the application `code`. + if instr.peerCtrl == Stopped and not desc.ctrl.stopped: + desc.ctrl.stopped = true + else: + iError = rc.error + + template isAvailable(_: typeof instr): bool {.used.} = rc.isOk() + + code + + if rc.isOk(): + doAssert not desc.message.isNil + doAssert desc.message.recType == dataType + doAssert instr.serial == desc.iNum + + desc.checkSyncerState(instr, ignLatestNum, info) + + debug info & ": got data", n=desc.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer, peerID, dataType + + desc.message = M(nil) + + discard # no-op, visual alignment + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim new file mode 100644 index 0000000000..1d2ceb365d --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim @@ -0,0 +1,209 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + std/tables, + pkg/[chronicles, chronos, eth/common], + ../../replay_desc, + ./dispatch_helpers + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Private helper +# ------------------------------------------------------------------------------ + +proc schedDaemonProcessImpl( + daemon: ReplayDaemonRef; + instr: TraceSchedDaemonBegin; + info: static[string]; + ) {.async: (raises: []).} = + ## Run the task `schedDaemon()`. This function has to be run background + ## process (using `asyncSpawn`.) + ## + let run = daemon.run + trace info & ": begin", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, syncState=instr.syncState + + discard await run.worker.schedDaemon(run.ctx) + daemon.processFinished(instr, info) + + trace info & ": end", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, syncState=instr.syncState + + +proc schedPeerProcessImpl( + buddy: ReplayBuddyRef; + instr: TraceSchedPeerBegin; + info: static[string]; + ) {.async: (raises: []).} = + ## Run the task `schedPeer()`. This function has to be run background + ## process (using `asyncSpawn`.) + ## + let run = buddy.run + trace info & ": begin", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short, + syncState=instr.syncState + + # Activate peer + buddy.run.nPeers.inc + + discard await run.worker.schedPeer(buddy) + buddy.processFinished(instr, info) + + trace info & ": end", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short, + syncState=instr.syncState + +# ------------------------------------------------------------------------------ +# Public dispatcher handlers +# ------------------------------------------------------------------------------ + +proc schedDaemonBegin*( + run: ReplayRunnerRef; + instr: TraceSchedDaemonBegin; + info: static[string]; + ) {.async: (raises: []).} = + ## Run the `schedDaemon()` task. + ## + # Synchronise against captured environment and start process + let daemon = run.newDaemonFrame(instr, info).valueOr: return + discard await daemon.waitForSyncedEnv(instr, info) + asyncSpawn daemon.schedDaemonProcessImpl(instr, info) + + +proc schedDaemonEnd*( + run: ReplayRunnerRef; + instr: TraceSchedDaemonEnd; + info: static[string]; + ) {.async: (raises: []).} = + ## Clean up (in foreground) after `schedDaemon()` process has terminated. + ## + let daemon = run.getDaemon(info).valueOr: return + daemon.whenProcessFinished(instr, info).isErrOr: + daemon.delDaemon(info) # Clean up + + +proc schedStartWorker*( + run: ReplayRunnerRef; + instr: TraceSchedStart; + info: static[string]; + ) = + ## Runs `schedStart()` in the foreground. + ## + let + buddy = run.newPeer(instr, info) + accept = run.worker.schedStart(buddy) + + trace info & ": begin", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + + if accept != instr.accept: + warn info & ": result argument differs", n=run.iNum, serial=instr.serial, + peer=buddy.peer, expected=instr.accept, result=accept + + # Syncer state was captured when leaving the `schedStart()` handler. + buddy.checkSyncerState(instr, ignLatestNum=true, info) # relaxed check + + if not accept: + buddy.delPeer(info) # Clean up + + trace info & ": end", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + + +proc schedStopWorker*( + run: ReplayRunnerRef; + instr: TraceSchedStop; + info: static[string]; + ) = + ## Runs `schedStop()` in the foreground. + ## + let buddy = run.getOrNewPeerFrame(instr, info) + run.worker.schedStop(buddy) + + # As the `schedStop()` function environment was captured only after the + # syncer was activated, there might still be some unregistered peers hanging + # around. So it is perfectly OK to see the peer for the first time, here + # which has its desciptor sort of unintialised (relative to `instr`.) + if not buddy.isNew: + # Syncer state was captured when leaving the `schedStop()` handler. + if instr.peerCtrl == Stopped and not buddy.ctrl.stopped: + buddy.ctrl.stopped = true + buddy.checkSyncerState(instr, info) + + # Clean up + buddy.delPeer(info) + + trace info & ": done", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + + +proc schedPoolWorker*( + run: ReplayRunnerRef; + instr: TraceSchedPool; + info: static[string]; + ) = + ## Runs `schedPool()` in the foreground. + ## + let buddy = run.getOrNewPeerFrame(instr, info) + + if 0 < run.nPeers: + warn info & ": no active peers allowed", n=run.iNum, serial=instr.serial, + peer=buddy.peer, nPeers=run.nPeers, expected=0 + + # The scheduler will reset the `poolMode` flag before starting the + # `schedPool()` function. + run.ctx.poolMode = false + + discard run.worker.schedPool(buddy, instr.last, instr.laps.int) + + # Syncer state was captured when leaving the `schedPool()` handler. + buddy.checkSyncerState(instr, info) + + # Reset frame data + buddy.frameID = 0 + + info info & ": done", n=run.iNum, serial=instr.serial, + frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + + +proc schedPeerBegin*( + run: ReplayRunnerRef; + instr: TraceSchedPeerBegin; + info: static[string]; + ) {.async: (raises: []).} = + ## Run the `schedPeer()` task. + ## + # Synchronise against captured environment and start process + let buddy = run.getOrNewPeerFrame(instr, info) + discard await buddy.waitForSyncedEnv(instr, info) + asyncSpawn buddy.schedPeerProcessImpl(instr, info) + + +proc schedPeerEnd*( + run: ReplayRunnerRef; + instr: TraceSchedPeerEnd; + info: static[string]; + ) {.async: (raises: []).} = + ## Clean up (in foreground) after `schedPeer()` process has terminated. + ## + let buddy = run.getPeer(instr, info).valueOr: return + buddy.whenProcessFinished(instr, info).isErrOr: + buddy.run.nPeers.dec # peer is not active, anymore + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim new file mode 100644 index 0000000000..36b5218312 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim @@ -0,0 +1,91 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/chronicles, + ../../../../../execution_chain/core/chain, + ../../replay_desc, + ./dispatch_helpers + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Public dispatcher handlers +# ------------------------------------------------------------------------------ + +proc syncActvFailedWorker*( + run: ReplayRunnerRef; + instr: TraceSyncActvFailed; + info: static[string]; + ) = + trace info, n=run.iNum, serial=instr.serial + + +proc syncActivateWorker*( + run: ReplayRunnerRef; + instr: TraceSyncActivated; + info: static[string]) = + let + serial = instr.serial + ctx = run.ctx + + if not ctx.hibernate: + warn info & ": already activated", n=run.iNum, serial + return + + var activationOK = true + if ctx.chain.baseNumber != instr.baseNum: + error info & ": cannot activate (bases must match)", n=run.iNum, serial, + base=ctx.chain.baseNumber.bnStr, expected=instr.baseNum.bnStr + activationOK = false + + if activationOK: + ctx.hdrCache.headTargetUpdate(instr.head, instr.finHash) + + # Set the number of active buddies (avoids some moaning.) + run.ctx.pool.nBuddies = instr.nPeers.int + run.checkSyncerState(instr, info) + + if ctx.hibernate or not activationOK: + const failedInfo = info & ": activation failed" + trace failedInfo, n=run.iNum, serial + run.stopError(failedInfo) + else: + # No need for scheduler noise (e.g. disconnect messages.) + ctx.noisyLog = false + debug info, n=run.iNum, serial + + +proc syncSuspendWorker*( + run: ReplayRunnerRef; + instr: TraceSyncHibernated; + info: static[string]; + ) = + if not run.ctx.hibernate: + run.stopError(info & ": suspend failed") + return + + run.checkSyncerState(instr, info) + debug info, n=run.iNum, serial=instr.serial + + # Shutdown if there are no remaining sessions left + if 1 < run.nSessions: + run.nSessions.dec + else: + run.stopOk(info & ": session(s) terminated") + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim new file mode 100644 index 0000000000..ba680257b4 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim @@ -0,0 +1,76 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/chronicles, + ../../../../../execution_chain/core/chain, + ../../replay_desc, + ./dispatch_helpers + +logScope: + topics = "replay runner" + +# ------------------------------------------------------------------------------ +# Public dispatcher handlers +# ------------------------------------------------------------------------------ + +proc versionInfoWorker*( + run: ReplayRunnerRef; + instr: TraceVersionInfo; + info: static[string]; + ) = + let + serial = instr.serial + ctx = run.ctx + var + versionOK = true + + if serial != 1: + error info & ": not the first record", serial, expected=1 + versionOK = false + + if run.instrNumber != 1: + error info & ": record count mismatch", n=run.instrNumber, expected=1 + versionOK = false + + if instr.version != TraceVersionID: + error info & ": wrong version", serial, + traceLayoutVersion=instr.version, expected=TraceVersionID + versionOK = false + + if instr.networkId != ctx.chain.com.networkId: + error info & ": wrong network", serial, + networkId=instr.networkId, expected=ctx.chain.com.networkId + versionOK = false + + if ctx.chain.baseNumber < instr.baseNum: + error info & ": cannot start (base too low)", serial, + base=ctx.chain.baseNumber.bnStr, replayBase=instr.baseNum.bnStr + versionOK = false + + if not ctx.hibernate: + error info & ": syncer must not be activated, yet", serial + versionOK = false + + if not versionOK: + run.stopError(info & ": version match failed") + return + + chronicles.info info, n=run.iNum, serial, TraceVersionID, + base=ctx.chain.baseNumber.bnStr, latest=ctx.chain.latestNumber.bnStr + run.checkSyncerState(instr, info) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_init.nim b/tools/syncer/replay/replay_runner/runner_init.nim new file mode 100644 index 0000000000..9b452d0661 --- /dev/null +++ b/tools/syncer/replay/replay_runner/runner_init.nim @@ -0,0 +1,80 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Replay runner + +{.push raises:[].} + +import + pkg/chronos, + ../../../../execution_chain/networking/[p2p, p2p_peers, peer_pool], + ../../../../execution_chain/sync/wire_protocol, + ../replay_desc + +logScope: + topics = "replay" + +# ------------------------------------------------------------------------------ +# Private helper(s) +# ------------------------------------------------------------------------------ + +proc getDispatcher(): Dispatcher = + ## Return a list of all known protocols and pretend all are supported + var po = PeerObserver() + po.addProtocol eth68 + po.addProtocol eth69 + + var q: array[MAX_PROTOCOLS,Opt[uint64]] + q[0] = Opt.none(uint64) + q[1] = Opt.some(16'u64) + for n in 2 .. po.protocols.len: + q[n] = Opt.some(q[n-1].value + po.protocols[n-1].messages[^1].id) + + Dispatcher(protocolOffsets: q) + + +proc getProtocolStates(): array[MAX_PROTOCOLS,RootRef] = + ## Pretend that all `getDispatcher()` list items are initialised + var q: typeof(result) + q[0] = RootRef(nil) + q[1] = EthPeerState(initialized: true) + q[2] = Eth69PeerState(initialized: true) + q + + +proc init(T: type ReplayEthState): T = + ## For ethxx compatibility + T(capa: getDispatcher(), + prots: getProtocolStates()) + +# ------------------------------------------------------------------------------ +# Public constructor(s) +# ------------------------------------------------------------------------------ + +proc init*(T: type ReplayRunnerRef; rpl: ReplayRef): T = + ## Initialise dispatcher + const info = "ReplayRunnerRef(): " + if ReplayRunnerID != rpl.ctx.handler.version: + fatal info & "Need original handlers version", + handlerVersion=rpl.ctx.handler.version + quit(QuitFailure) + + T(ctx: rpl.ctx, + worker: rpl.backup, + ethState: ReplayEthState.init(), + fakeImport: rpl.fakeImport) + + +proc destroy*(run: ReplayRunnerRef) = + discard + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_setup.nim b/tools/syncer/replay/replay_setup.nim new file mode 100644 index 0000000000..33f38f9d04 --- /dev/null +++ b/tools/syncer/replay/replay_setup.nim @@ -0,0 +1,195 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at +# https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at +# https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. + +## Trace environment setup & destroy + +{.push raises:[].} + +import + std/[streams, strutils], + pkg/[chronicles, chronos], + ../../../execution_chain/sync/wire_protocol, + ./replay_reader/reader_init, + ./replay_runner/runner_dispatch/[dispatch_blocks, dispatch_headers], + ./replay_runner/runner_init, + ./[replay_desc, replay_runner] + +logScope: + topics = "beacon replay" + +const + DontQuit = low(int) + ## To be used with `onCloseException()` + + stopInfo = "replayStop()" + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +template onException( + info: static[string]; + quitCode: static[int]; + code: untyped) = + try: + code + except CatchableError as e: + const blurb = info & "Replay stream exception -- STOP" + when quitCode == DontQuit: + error blurb, error=($e.name), msg=e.msg + else: + fatal blurb, error=($e.name), msg=e.msg + quit(quitCode) + +# ------------------------------------------------------------------------------ +# Private replacement handlers +# ------------------------------------------------------------------------------ + +proc noOpBuddy(buddy: BeaconBuddyRef) = + discard + +proc noOpSchedStartFalse(buddy: BeaconBuddyRef): bool = + return false + +proc noOpSchedPoolTrue(a: BeaconBuddyRef, b: bool, c: int): bool = + return true + +proc noOpSchedDaemon(ctx: BeaconCtxRef): + Future[Duration] {.async: (raises: []).} = + return replayWaitMuted + +proc noOpSchedPeer(buddy: BeaconBuddyRef): + Future[Duration] {.async: (raises: []).} = + return replayWaitMuted + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +proc checkStop(rpl: ReplayRef): ReplayStopIfFn = + proc(): bool = + if rpl.runner.isNil or + rpl.runner.stopRunner: + return true + false + +proc cleanUp(rpl: ReplayRef): ReplayEndUpFn = + proc() = + rpl.stopSync(rpl) + if rpl.stopQuit: + notice stopInfo & ": quitting .." + quit(QuitSuccess) + else: + info stopInfo & ": terminating .." + +# -------------- + +proc replayStartCB(rpl: ReplayRef) = + ## Start replay emulator + ## + rpl.reader = ReplayReaderRef.init(rpl.captStrm) + rpl.runner = ReplayRunnerRef.init(rpl) + + # Set up redirect handlers for replay + rpl.version = ReplayRunnerID + # activate # use as is + # suspend # use as is + rpl.schedDaemon = noOpSchedDaemon + rpl.schedStart = noOpSchedStartFalse # `false` => don't register + rpl.schedStop = noOpBuddy + rpl.schedPool = noOpSchedPoolTrue # `true` => stop repeating + rpl.schedPeer = noOpSchedPeer + rpl.getBlockHeaders = fetchHeadersHandler # from dispatcher + rpl.syncBlockHeaders = noOpBuddy + rpl.getBlockBodies = fetchBodiesHandler # from dispatcher + rpl.syncBlockBodies = noOpBuddy + rpl.importBlock = importBlockHandler # from dispatcher + rpl.syncImportBlock = noOpBuddy + + rpl.startSync = proc(self: BeaconHandlersSyncRef) = + discard + + rpl.stopSync = proc(self: BeaconHandlersSyncRef) = + ReplayRef(self).reader.destroy() + ReplayRef(self).runner.destroy() + stopInfo.onException(DontQuit): + ReplayRef(self).captStrm.close() + ReplayRef(self).ctx.pool.handlers = ReplayRef(self).backup + + # Start fake scheduler + asyncSpawn rpl.runner.runDispatcher( + rpl.reader, stopIf=rpl.checkStop, endUp=rpl.cleanUp) + +# ------------------------------------------------------------------------------ +# Public constructor/destructor +# ------------------------------------------------------------------------------ + +proc replaySetup*( + ctx: BeaconCtxRef; + fileName: string; + noStopQuit: bool; + fakeImport: bool; + ): Result[void,string] = + ## .. + const info = "replaySetup(): " + + if ctx.handler.version != 0: + return err("Overlay session handlers activated already" & + "ID=" & $ctx.handler.version) + + let strm = fileName.newFileStream fmRead + if strm.isNil: + return err("Cannot open trace file for reading" & + ", fileName=\"" & fileName & "\"") + + let rpl = ReplayRef( + ctx: ctx, + captStrm: strm, + fakeImport: fakeImport, + stopQuit: not noStopQuit, + backup: ctx.pool.handlers, + + # This is still the old descriptor which will be updated when + # `startSync()` is run. + version: ReplayRunnerID, + activate: ctx.handler.activate, + suspend: ctx.handler.suspend, + schedDaemon: ctx.handler.schedDaemon, + schedStart: ctx.handler.schedStart, + schedStop: ctx.handler.schedStop, + schedPool: ctx.handler.schedPool, + schedPeer: ctx.handler.schedPeer, + getBlockHeaders: ctx.handler.getBlockHeaders, + syncBlockHeaders: ctx.handler.syncBlockHeaders, + getBlockBodies: ctx.handler.getBlockBodies, + syncBlockBodies: ctx.handler.syncBlockBodies, + importBlock: ctx.handler.importBlock, + syncImportBlock: ctx.handler.syncImportBlock) + + rpl.startSync = proc(self: BeaconHandlersSyncRef) = + ReplayRef(self).replayStartCB() + + rpl.stopSync = proc(self: BeaconHandlersSyncRef) = + info.onException(DontQuit): + ReplayRef(self).captStrm.close() + ReplayRef(self).ctx.pool.handlers = ReplayRef(self).backup + + ctx.pool.handlers = rpl + ok() + + +proc replayRelease*(ctx: BeaconCtxRef) = + ## Stop replay and restore descriptors + if ctx.pool.handlers.version in {ReplaySetupID, ReplayRunnerID}: + ReplayRef(ctx.pool.handlers).stopSync(nil) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ From 0f6abbaee599ecfbc66e396c9e12f2b85ee27566 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Thu, 28 Aug 2025 17:12:42 +0100 Subject: [PATCH 07/18] Provide command line capture replay tool details This tool wraps and runs the `nimbus_execution_client` with the sync scheduler partially superseded by a capturing state data replay facility. --- Makefile | 2 +- tools/syncer/replay/README.md | 32 +++- tools/syncer/syncer_test_client_replay.nim | 141 ++++++++++++++++++ .../syncer/syncer_test_client_replay.nim.cfg | 5 + 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 tools/syncer/syncer_test_client_replay.nim create mode 100644 tools/syncer/syncer_test_client_replay.nim.cfg diff --git a/Makefile b/Makefile index 30e22bc618..184b5ff9f8 100644 --- a/Makefile +++ b/Makefile @@ -389,7 +389,7 @@ txparse: | build deps # build syncer debugging and analysis tools SYNCER_TOOLS_DIR := tools/syncer -SYNCER_TOOLS := $(foreach name,trace inspect,syncer_test_client_$(name)) +SYNCER_TOOLS := $(foreach name,trace inspect replay,syncer_test_client_$(name)) .PHONY: syncer-tools syncer-tools-clean $(SYNCER_TOOLS) syncer-tools: $(SYNCER_TOOLS) syncer-tools-clean: diff --git a/tools/syncer/replay/README.md b/tools/syncer/replay/README.md index ec9fd272ba..8cc1e9bb40 100644 --- a/tools/syncer/replay/README.md +++ b/tools/syncer/replay/README.md @@ -5,6 +5,36 @@ Inspection ---------- Given a (probably gzipped) capture file **(capture)** as a result of - tracing, its content can be visualised via +tracing, its content can be visualised via ./build/syncer_test_client_inspect (capture) + +Replay +------ + +Copy and secure the current database directory **(database)** as **(dbcopy)**, +say. Then start a capture run on the original data base as + + ./build/syncer_test_client_replay \ + --datadir=(database) ... -- (capture) + +where **(capture)** will contain all the data for the replay. This file can +bebome quite big (e.g. 30GiB for the last 120k blocks synchronised on +*mainnet*) but can be gzipped after the capture run was stopped. + +Monitor the capture run so it can be stopped at an an appropriate state using +metrics or logs. With the above command line argumants, only the next sync +session is logged ranging from the activation message (when *Activating syncer* +is logged) up intil the suspend message (when *Suspending syncer* is logged.) + +Now, the captured run can be replayed on the secured database copy +**(dbcopy)** with the (probably gzipped) **(capture)** file via + + ./build/syncer_test_client_replay \ + --datadir=(dbcopy) ... -- (capture) + +where ihe additional arguments **...** of either command above need not be +the same. + +Note that you need another copy of **(database)** if you need to re-exec the +latter command line statement. diff --git a/tools/syncer/syncer_test_client_replay.nim b/tools/syncer/syncer_test_client_replay.nim new file mode 100644 index 0000000000..23ece06cfd --- /dev/null +++ b/tools/syncer/syncer_test_client_replay.nim @@ -0,0 +1,141 @@ +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + std/[cmdline, os, strutils], + pkg/[chronicles, results], + ../../execution_chain/config, + ../../execution_chain/sync/beacon, + ./helpers/[nimbus_el_wrapper, sync_ticker], + ./replay/replay_setup + +type + ArgsDigest = tuple + elArgs: seq[string] # split command line: left to "--" marker + fileName: string # capture file name + noStopQuit: bool # capture modifier argument + fakeImport: bool # ditto + syncTicker: bool # .. + +let + cmdName = getAppFilename().extractFilename() + +# ------------------------------------------------------------------------------ +# Private helpers, command line parsing tools +# ------------------------------------------------------------------------------ + +proc argsCheck(q: seq[string]): seq[string] = + if q.len == 0 or + q[0] == "-h" or + q[0] == "--help": + echo "", + "Usage: ", cmdName, + " [.. --] [..]\n", + " Capture file:\n", + " Read from trace capture file and replay the\n", + " syncer session.", + " argument.\n", + " Attributes:\n", + " noStopQuit Continue as normal after the captured replay\n", + " states are exhausted. Otherwise the program\n", + " will terminate.\n", + " fakeImport Will not import blocks while replaying\n.", + " syncTicker Log sync state regularly.\n" + quit(QuitFailure) + return q + +proc argsError(s: string) = + echo "*** ", cmdName, ": ", s, "\n" + discard argsCheck(@["-h"]) # usage & quit + +# ------------- + +proc parseCmdLine(): ArgsDigest = + ## Parse command line: + ## :: + ## [.. --] [noStopQuit] .. + ## + var exArgs: seq[string] + + # Split command line by "--" into `exArgs[]` and `elArgs[]` + let args = commandLineParams().argsCheck() + for n in 0 ..< args.len: + if args[n] == "--": + if 0 < n: + result.elArgs = args[0 .. n-1] + if n < args.len: + exArgs = args[n+1 .. ^1].argsCheck() + break + + # Case: no delimiter "--" given + if exArgs.len == 0 and result.elArgs.len == 0: + exArgs = args + + result.fileName = exArgs[0] + for n in 1 ..< exArgs.len: + let w = exArgs[n].split('=',2) + + block: + # noStopQuit + const token = "noStopQuit" + if toLowerAscii(w[0]) == toLowerAscii(token): + if 1 < w.len: + argsError("Sub-argument has no value: " & token) + result.noStopQuit = true + continue + + block: + # fakeImport + const token = "fakeImport" + if toLowerAscii(w[0]) == toLowerAscii(token): + if 1 < w.len: + argsError("Sub-argument has no value: " & token) + result.fakeImport = true + continue + + block: + # syncTicker + const token = "syncTicker" + if toLowerAscii(w[0]) == toLowerAscii(token): + if 1 < w.len: + argsError("Sub-argument has no value: " & token) + result.syncTicker = true + continue + + argsError("Sub-argument unknown: " & exArgs[n]) + +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ + +proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = + return proc(desc: BeaconSyncRef) = + if args.syncTicker: + desc.ctx.pool.ticker = syncTicker() + desc.ctx.replaySetup( + fileName = args.fileName, + noStopQuit = args.noStopQuit, + fakeImport = args.fakeImport).isOkOr: + fatal "Cannot set up replay handlers", error + quit(QuitFailure) + +# ------------------------------------------------------------------------------ +# Main +# ------------------------------------------------------------------------------ + +# Pre-parse command line +let argsDigest = parseCmdLine() + +# Early plausibility check +if not argsDigest.fileName.fileExists: + argsError("No such capture file: \"" & argsDigest.fileName & "\"") + +# Processing left part command line arguments +let conf = makeConfig(cmdLine = argsDigest.elArgs) + +# Run execution client +conf.runNimbusExeClient(argsDigest.beaconSyncConfig) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/syncer_test_client_replay.nim.cfg b/tools/syncer/syncer_test_client_replay.nim.cfg new file mode 100644 index 0000000000..580b97645a --- /dev/null +++ b/tools/syncer/syncer_test_client_replay.nim.cfg @@ -0,0 +1,5 @@ +-d:"chronicles_sinks=textlines[stderr]" +-d:"chronicles_runtime_filtering=on" +-d:"chronicles_line_numbers=1" +-d:"chronicles_thread_ids=no" +-d:"chronicles_log_level=TRACE" From effddef06e8c3762ef0294ee48be14b1b3ff40cf Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Mon, 8 Sep 2025 09:13:25 +0100 Subject: [PATCH 08/18] Fix copyright years --- tools/syncer/helpers/nimbus_el_wrapper.nim | 11 +++++++++-- tools/syncer/helpers/sync_ticker.nim | 5 ++--- tools/syncer/syncer_test_client_inspect.nim | 2 +- tools/syncer/syncer_test_client_replay.nim | 6 ++++++ tools/syncer/syncer_test_client_trace.nim | 6 ++++++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/tools/syncer/helpers/nimbus_el_wrapper.nim b/tools/syncer/helpers/nimbus_el_wrapper.nim index 3cccf434bf..d004995e8d 100644 --- a/tools/syncer/helpers/nimbus_el_wrapper.nim +++ b/tools/syncer/helpers/nimbus_el_wrapper.nim @@ -1,5 +1,12 @@ -# This file may not be copied, modified, or distributed except according to -# those terms. +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or +# http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed +# except according to those terms. ## Wrapper to expose `run()` from `nimbus_execution_client.nim` without ## marking is exportable. diff --git a/tools/syncer/helpers/sync_ticker.nim b/tools/syncer/helpers/sync_ticker.nim index 21b0250ecd..4bace135f9 100644 --- a/tools/syncer/helpers/sync_ticker.nim +++ b/tools/syncer/helpers/sync_ticker.nim @@ -1,6 +1,5 @@ -# Nimbus - Fetch account and storage states from peers efficiently -# -# Copyright (c) 2021-2025 Status Research & Development GmbH +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) diff --git a/tools/syncer/syncer_test_client_inspect.nim b/tools/syncer/syncer_test_client_inspect.nim index 7efe916944..1605658aa9 100644 --- a/tools/syncer/syncer_test_client_inspect.nim +++ b/tools/syncer/syncer_test_client_inspect.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2018-2025 Status Research & Development GmbH +# Copyright (c) 2025 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) diff --git a/tools/syncer/syncer_test_client_replay.nim b/tools/syncer/syncer_test_client_replay.nim index 23ece06cfd..bdc858ee7e 100644 --- a/tools/syncer/syncer_test_client_replay.nim +++ b/tools/syncer/syncer_test_client_replay.nim @@ -1,3 +1,9 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. # This file may not be copied, modified, or distributed except according to # those terms. diff --git a/tools/syncer/syncer_test_client_trace.nim b/tools/syncer/syncer_test_client_trace.nim index b63b6d3113..0ea1f896fe 100644 --- a/tools/syncer/syncer_test_client_trace.nim +++ b/tools/syncer/syncer_test_client_trace.nim @@ -1,3 +1,9 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. # This file may not be copied, modified, or distributed except according to # those terms. From 765d0ee9aea04d3ec47e356e3583438b60717531 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Tue, 9 Sep 2025 10:01:15 +0100 Subject: [PATCH 09/18] Use `confutils` for command line options management why No need for extra command line parsing stuff --- tools/syncer/.gitignore | 1 + tools/syncer/helpers/nimbus_el_wrapper.nim | 37 ---- tools/syncer/replay/README.md | 6 +- tools/syncer/syncer_test_client_inspect.nim | 56 ++---- tools/syncer/syncer_test_client_replay.nim | 163 +++++++---------- tools/syncer/syncer_test_client_trace.nim | 184 ++++++++------------ tools/syncer/trace/README.md | 2 +- 7 files changed, 154 insertions(+), 295 deletions(-) create mode 100644 tools/syncer/.gitignore delete mode 100644 tools/syncer/helpers/nimbus_el_wrapper.nim diff --git a/tools/syncer/.gitignore b/tools/syncer/.gitignore new file mode 100644 index 0000000000..2d19fc766d --- /dev/null +++ b/tools/syncer/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/tools/syncer/helpers/nimbus_el_wrapper.nim b/tools/syncer/helpers/nimbus_el_wrapper.nim deleted file mode 100644 index d004995e8d..0000000000 --- a/tools/syncer/helpers/nimbus_el_wrapper.nim +++ /dev/null @@ -1,37 +0,0 @@ -# Nimbus -# Copyright (c) 2025 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or -# http://www.apache.org/licenses/LICENSE-2.0) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or -# http://opensource.org/licenses/MIT) -# at your option. This file may not be copied, modified, or distributed -# except according to those terms. - -## Wrapper to expose `run()` from `nimbus_execution_client.nim` without -## marking is exportable. - -include # (!) - ../../../execution_chain/nimbus_execution_client - -proc runNimbusExeClient*(conf: NimbusConf; cfgCB: BeaconSyncConfigHook) = - ## Wrapper, make it public for debugging - ProcessState.setupStopHandlers() - - # Set up logging before everything else - setupLogging(conf.logLevel, conf.logStdout, none(OutFile)) - setupFileLimits() - - # TODO provide option for fixing / ignoring permission errors - if not checkAndCreateDataDir(conf.dataDir): - # We are unable to access/create data folder or data folder's - # permissions are insecure. - quit QuitFailure - - let nimbus = NimbusNode( - ctx: newEthContext(), - beaconSyncRef: BeaconSyncRef.init cfgCB) - - nimbus.run(conf) - -# End diff --git a/tools/syncer/replay/README.md b/tools/syncer/replay/README.md index 8cc1e9bb40..e0787d8621 100644 --- a/tools/syncer/replay/README.md +++ b/tools/syncer/replay/README.md @@ -7,7 +7,7 @@ Inspection Given a (probably gzipped) capture file **(capture)** as a result of tracing, its content can be visualised via - ./build/syncer_test_client_inspect (capture) + ./build/syncer_test_client_inspect --capture-file=(capture) Replay ------ @@ -16,7 +16,7 @@ Copy and secure the current database directory **(database)** as **(dbcopy)**, say. Then start a capture run on the original data base as ./build/syncer_test_client_replay \ - --datadir=(database) ... -- (capture) + --datadir=(database) ... -- --capture-file=(capture) where **(capture)** will contain all the data for the replay. This file can bebome quite big (e.g. 30GiB for the last 120k blocks synchronised on @@ -31,7 +31,7 @@ Now, the captured run can be replayed on the secured database copy **(dbcopy)** with the (probably gzipped) **(capture)** file via ./build/syncer_test_client_replay \ - --datadir=(dbcopy) ... -- (capture) + --datadir=(dbcopy) ... -- --capture-file=(capture) where ihe additional arguments **...** of either command above need not be the same. diff --git a/tools/syncer/syncer_test_client_inspect.nim b/tools/syncer/syncer_test_client_inspect.nim index 1605658aa9..857f95ee3a 100644 --- a/tools/syncer/syncer_test_client_inspect.nim +++ b/tools/syncer/syncer_test_client_inspect.nim @@ -8,53 +8,35 @@ # those terms. import - std/[cmdline, os, streams, strutils], - pkg/chronicles, + std/[cmdline, os, streams, strutils, terminal], + pkg/[chronicles, confutils], pkg/beacon_chain/process_state, ./replay/replay_reader -let cmdName = getAppFilename().extractFilename() +const + fgSection = fgYellow -# ------------------------------------------------------------------------------ -# Private helpers, command line parsing tools -# ------------------------------------------------------------------------------ - -proc argsCheck(q: seq[string]): seq[string] = - if q.len == 0 or - q[0] == "-h" or - q[0] == "--help": - echo "", - "Usage: ", cmdName, " [--] \n", - " Capture file:\n", - " Read from argument and print its contents." - quit(QuitFailure) - q - -proc argsError(s: string) = - echo "*** ", cmdName, ": ", s, "\n" - discard argsCheck(@["-h"]) # usage & quit - -# ------------- - -proc parseCmdLine(): string = - var args = commandLineParams().argsCheck - if args[0] == "--": - if args.len == 1: - argsError("Missing capture file argument") - args = args[1 .. ^1] - if args.len == 0: - argsError("Missing capture file argiment") - if 1 < args.len: - argsError("Extra arguments: " & args[1 .. ^1].join(" ") & ".") - return args[0] +type + ToolConfig* = object of RootObj + captureFile {. + separator: "INSPECT TOOL OPTIONS:" + desc: "Read from argument and print its contents" + name: "capture-file" .}: InputFile # ------------------------------------------------------------------------------ # Main # ------------------------------------------------------------------------------ -let name = parseCmdLine() +let + config = ToolConfig.load( + cmdLine = commandLineParams(), + copyrightBanner = ansiForegroundColorCode(fgSection) & + "\pNimbus capture file inspection tool.\p") + name = config.captureFile.string + if not name.fileExists: - argsError("No such capture file: \"" & name & "\"") + fatal "No such capture file", name + quit(QuitFailure) ProcessState.setupStopHandlers() ProcessState.notifyRunning() diff --git a/tools/syncer/syncer_test_client_replay.nim b/tools/syncer/syncer_test_client_replay.nim index bdc858ee7e..28e807d536 100644 --- a/tools/syncer/syncer_test_client_replay.nim +++ b/tools/syncer/syncer_test_client_replay.nim @@ -8,120 +8,73 @@ # those terms. import - std/[cmdline, os, strutils], + std/[cmdline, os, strutils, terminal], pkg/[chronicles, results], - ../../execution_chain/config, + ../../execution_chain/[config, nimbus_desc, nimbus_execution_client], ../../execution_chain/sync/beacon, - ./helpers/[nimbus_el_wrapper, sync_ticker], + ./helpers/sync_ticker, ./replay/replay_setup -type - ArgsDigest = tuple - elArgs: seq[string] # split command line: left to "--" marker - fileName: string # capture file name - noStopQuit: bool # capture modifier argument - fakeImport: bool # ditto - syncTicker: bool # .. +const + fgSection = fgYellow + fgOption = fgBlue -let - cmdName = getAppFilename().extractFilename() +type + ToolConfig* = object of RootObj + captureFile {. + separator: "REPLAY TOOL OPTIONS:" + desc: "Read from trace capture file and replay the " & + " syncer session" + name: "capture-file" .}: InputFile + + noStopQuit {. + desc: "Continue as normal after the captured replay states are " & + "exhausted. If the option is given, the program will terminate" + defaultValue: false + name: "no-stop-quit" .}: bool + + fakeImport {. + desc: "The tool will not import blocks while replaying" + defaultValue: false + name: "enable-sync-ticker" .}: bool + + noSyncTicker {. + desc: "Disable logging sync status regularly" + defaultValue: false + name: "disable-sync-ticker" .}: bool + + SplitCmdLine = tuple + leftArgs: seq[string] # split command line: left to "--" marker (nimbus) + rightArgs: seq[string] # split command line: right to "--" marker (tool) # ------------------------------------------------------------------------------ # Private helpers, command line parsing tools # ------------------------------------------------------------------------------ -proc argsCheck(q: seq[string]): seq[string] = - if q.len == 0 or - q[0] == "-h" or - q[0] == "--help": - echo "", - "Usage: ", cmdName, - " [.. --] [..]\n", - " Capture file:\n", - " Read from trace capture file and replay the\n", - " syncer session.", - " argument.\n", - " Attributes:\n", - " noStopQuit Continue as normal after the captured replay\n", - " states are exhausted. Otherwise the program\n", - " will terminate.\n", - " fakeImport Will not import blocks while replaying\n.", - " syncTicker Log sync state regularly.\n" - quit(QuitFailure) - return q - -proc argsError(s: string) = - echo "*** ", cmdName, ": ", s, "\n" - discard argsCheck(@["-h"]) # usage & quit - -# ------------- - -proc parseCmdLine(): ArgsDigest = - ## Parse command line: +proc splitCmdLine(): SplitCmdLine = + ## Split commans line options ## :: - ## [.. --] [noStopQuit] .. + ## [ --] [ delimiter "--" given - if exArgs.len == 0 and result.elArgs.len == 0: - exArgs = args - - result.fileName = exArgs[0] - for n in 1 ..< exArgs.len: - let w = exArgs[n].split('=',2) - - block: - # noStopQuit - const token = "noStopQuit" - if toLowerAscii(w[0]) == toLowerAscii(token): - if 1 < w.len: - argsError("Sub-argument has no value: " & token) - result.noStopQuit = true - continue - - block: - # fakeImport - const token = "fakeImport" - if toLowerAscii(w[0]) == toLowerAscii(token): - if 1 < w.len: - argsError("Sub-argument has no value: " & token) - result.fakeImport = true - continue - - block: - # syncTicker - const token = "syncTicker" - if toLowerAscii(w[0]) == toLowerAscii(token): - if 1 < w.len: - argsError("Sub-argument has no value: " & token) - result.syncTicker = true - continue - - argsError("Sub-argument unknown: " & exArgs[n]) + result.rightArgs = args[n+1 .. ^1] + return + result.rightArgs = args -# ------------------------------------------------------------------------------ -# Private functions -# ------------------------------------------------------------------------------ -proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = +proc beaconSyncConfig(conf: ToolConfig): BeaconSyncConfigHook = return proc(desc: BeaconSyncRef) = - if args.syncTicker: + if not conf.noSyncTicker: desc.ctx.pool.ticker = syncTicker() desc.ctx.replaySetup( - fileName = args.fileName, - noStopQuit = args.noStopQuit, - fakeImport = args.fakeImport).isOkOr: + fileName = conf.captureFile.string, + noStopQuit = conf.noStopQuit, + fakeImport = conf.fakeImport).isOkOr: fatal "Cannot set up replay handlers", error quit(QuitFailure) @@ -129,18 +82,26 @@ proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = # Main # ------------------------------------------------------------------------------ -# Pre-parse command line -let argsDigest = parseCmdLine() +let + (leftOpts, rightOpts) = splitCmdLine() + + rightConf = ToolConfig.load( + cmdLine = rightOpts, + copyrightBanner = ansiForegroundColorCode(fgSection) & + "\pNimbus execution layer with replay extension.\p" & + "Extended command line options:\p" & + ansiForegroundColorCode(fgOption) & + " [ --] []") + + leftConf = makeConfig(cmdLine = leftOpts) -# Early plausibility check -if not argsDigest.fileName.fileExists: - argsError("No such capture file: \"" & argsDigest.fileName & "\"") + nodeConf = leftConf.setupExeClientNode() -# Processing left part command line arguments -let conf = makeConfig(cmdLine = argsDigest.elArgs) +# Update node config for lazy beacon sync update +nodeConf.beaconSyncRef = BeaconSyncRef.init rightConf.beaconSyncConfig # Run execution client -conf.runNimbusExeClient(argsDigest.beaconSyncConfig) +nodeConf.runExeClient(leftConf) # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/syncer_test_client_trace.nim b/tools/syncer/syncer_test_client_trace.nim index 0ea1f896fe..c8d6d1177d 100644 --- a/tools/syncer/syncer_test_client_trace.nim +++ b/tools/syncer/syncer_test_client_trace.nim @@ -8,134 +8,78 @@ # those terms. import - std/[cmdline, os, strutils], - pkg/[chronicles, results], - ../../execution_chain/config, + std/[cmdline, os, strutils, terminal], + pkg/[chronicles, confutils, results], + ../../execution_chain/[config, nimbus_desc, nimbus_execution_client], ../../execution_chain/sync/beacon, - ./helpers/[nimbus_el_wrapper, sync_ticker], + ./helpers/sync_ticker, ./trace/trace_setup -type - ArgsDigest = tuple - elArgs: seq[string] # split command line: left to "--" marker - fileName: string # capture file name - nSessions: int # capture modifier argument - nPeersMin: int # ditto - syncTicker: bool # .. +const + fgSection = fgYellow + fgOption = fgBlue -let - cmdName = getAppFilename().extractFilename() +type + ToolConfig* = object of RootObj + captureFile {. + separator: "TRACE TOOL OPTIONS:" + desc: "Store captured states in the argument. If this " & + "option is missing, no capture file is written" + name: "capture-file" .}: Option[OutFile] + + nSessions {. + desc: "Run a trace for this many sessions (i.e. from activation to " & + "suspension)" + defaultValue: 1 + name: "num-trace-sessions" .}: uint + + nPeersMin {. + desc: "Minimal number of peers needed for activating the first syncer " & + "session" + defaultValue: 0 + name: "num-peers-min" .}: uint + + noSyncTicker {. + desc: "Disable logging sync status regularly" + defaultValue: false + name: "disable-sync-ticker" .}: bool + + SplitCmdLine = tuple + leftArgs: seq[string] # split command line: left to "--" marker (nimbus) + rightArgs: seq[string] # split command line: right to "--" marker (tool) # ------------------------------------------------------------------------------ -# Private helpers, command line parsing tools +# Private functions # ------------------------------------------------------------------------------ -proc argsCheck(q: seq[string]): seq[string] = - if q.len == 0 or - q[0] == "-h" or - q[0] == "--help": - echo "", - "Usage: ", cmdName, - " [.. --] [..]\n", - " Capture file:\n", - " Run a trace session and store captured states in the\n", - " argument.\n", - " Attributes:\n", - " nSessions=[0-9]+ Run a trace for this many sessions (i.e. from\n", - " activation to suspension). If set to 0, the\n", - " is ignored and will not be written.\n", - " However, other modifiers still have effcet.\n", - " nPeersMin=[0-9]+ Minimal number of peers needed for activating\n", - " the first syncer session.\n", - " syncTicker Log sync state regularly.\n" - quit(QuitFailure) - return q - -proc argsError(s: string) = - echo "*** ", cmdName, ": ", s, "\n" - discard argsCheck(@["-h"]) # usage & quit - -# ------------- - -proc parseCmdLine(): ArgsDigest = - ## Parse command line: +proc splitCmdLine(): SplitCmdLine = + ## Split commans line options ## :: - ## [.. --] [nSessions=[0-9]+] .. + ## [ --] [ delimiter "--" given - if exArgs.len == 0 and result.elArgs.len == 0: - exArgs = args - - result.fileName = exArgs[0] - result.nSessions = -1 - result.nPeersMin = -1 - for n in 1 ..< exArgs.len: - let w = exArgs[n].split('=',2) - - block: - # nSessions=[0-9]+ - const token = "nSessions" - if toLowerAscii(w[0]) == toLowerAscii(token): - if w.len < 2: - argsError("Sub-argument incomplete: " & token & "=[0-9]+") - try: - result.nSessions = int(w[1].parseBiggestUInt) - except ValueError as e: - argsError("Sub-argument value error: " & token & "=[0-9]+" & - ", error=" & e.msg) - continue - - block: - # nPeersMin=[0-9]+ - const token = "nPeersMin" - if toLowerAscii(w[0]) == toLowerAscii(token): - if w.len < 2: - argsError("Sub-argument incomplete: " & token & "=[0-9]+") - try: - result.nPeersMin = int(w[1].parseBiggestUInt) - except ValueError as e: - argsError("Sub-argument value error: " & token & "=[0-9]+" & - ", error=" & e.msg) - continue - - block: - # syncTicker - const token = "syncTicker" - if toLowerAscii(w[0]) == toLowerAscii(token): - if 1 < w.len: - argsError("Sub-argument has no value: " & token) - result.syncTicker = true - continue - - argsError("Sub-argument unknown: " & exArgs[n]) + result.rightArgs = args[n+1 .. ^1] + return + result.rightArgs = args -# ------------------------------------------------------------------------------ -# Private functions -# ------------------------------------------------------------------------------ -proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = +proc beaconSyncConfig(conf: ToolConfig): BeaconSyncConfigHook = return proc(desc: BeaconSyncRef) = - if args.syncTicker: + if not conf.noSyncTicker: desc.ctx.pool.ticker = syncTicker() - if 1 < args.nPeersMin: - desc.ctx.pool.minInitBuddies = args.nPeersMin - if args.nSessions == 0: + if 1 < conf.nPeersMin: + desc.ctx.pool.minInitBuddies = conf.nPeersMin.int + if conf.nSessions == 0 or + conf.captureFile.isNone: return desc.ctx.traceSetup( - fileName = args.fileName, - nSessions = max(0, args.nSessions)).isOkOr: + fileName = conf.captureFile.unsafeGet.string, + nSessions = conf.nSessions.int).isOkOr: fatal "Cannot set up trace handlers", error quit(QuitFailure) @@ -143,18 +87,26 @@ proc beaconSyncConfig(args: ArgsDigest): BeaconSyncConfigHook = # Main # ------------------------------------------------------------------------------ -# Pre-parse command line -let argsDigest = parseCmdLine() +let + (leftOpts, rightOpts) = splitCmdLine() + + rightConf = ToolConfig.load( + cmdLine = rightOpts, + copyrightBanner = ansiForegroundColorCode(fgSection) & + "\pNimbus execution layer with trace extension.\p" & + "Extended command line options:\p" & + ansiForegroundColorCode(fgOption) & + " [ --] []") + + leftConf = makeConfig(cmdLine = leftOpts) -# Early plausibility check -if argsDigest.fileName.fileExists: - argsError("Must not overwrite file: \"" & argsDigest.fileName & "\"") + nodeConf = leftConf.setupExeClientNode() -# Processing left part command line arguments -let conf = makeConfig(cmdLine = argsDigest.elArgs) +# Update node config for lazy beacon sync update +nodeConf.beaconSyncRef = BeaconSyncRef.init rightConf.beaconSyncConfig # Run execution client -conf.runNimbusExeClient(argsDigest.beaconSyncConfig) +nodeConf.runExeClient(leftConf) # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/trace/README.md b/tools/syncer/trace/README.md index 061aef127e..8eeaf87fce 100644 --- a/tools/syncer/trace/README.md +++ b/tools/syncer/trace/README.md @@ -5,7 +5,7 @@ For the *nimbus_execution_client* binary, data from a syncer sessions can be captured into a file **(capture)** along with system state information via - ./build/syncer_test_client_trace ... -- (capture) + ./build/syncer_test_client_trace ... -- --capture-file=(capture) where **...** stands for all other options that might be useful for running an execution layer session. From 4fceca7719191f9f2eaf641dcd58174703d075ac Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Wed, 10 Sep 2025 09:16:04 +0100 Subject: [PATCH 10/18] Bumb nim-zlib --- vendor/nim-zlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/nim-zlib b/vendor/nim-zlib index daa8723fd3..c71efff5fd 160000 --- a/vendor/nim-zlib +++ b/vendor/nim-zlib @@ -1 +1 @@ -Subproject commit daa8723fd32299d4ca621c837430c29a5a11e19a +Subproject commit c71efff5fd1721362b3363dc7d0e2a4c0dbc6453 From 2c02803c88131e72ddc3c9f234b6c9fd4e74a6d9 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Tue, 9 Sep 2025 13:51:35 +0100 Subject: [PATCH 11/18] Using gunzip from updated `nim-zlib` package --- .../replay/replay_reader/reader_desc.nim | 3 +- .../replay/replay_reader/reader_gunzip.nim | 352 ------------------ .../replay/replay_reader/reader_init.nim | 29 +- .../replay/replay_reader/reader_unpack.nim | 3 +- 4 files changed, 19 insertions(+), 368 deletions(-) delete mode 100644 tools/syncer/replay/replay_reader/reader_gunzip.nim diff --git a/tools/syncer/replay/replay_reader/reader_desc.nim b/tools/syncer/replay/replay_reader/reader_desc.nim index 3c48011fee..368a7c4192 100644 --- a/tools/syncer/replay/replay_reader/reader_desc.nim +++ b/tools/syncer/replay/replay_reader/reader_desc.nim @@ -14,8 +14,7 @@ import std/streams, - pkg/results, - ./reader_gunzip + pkg/[results, zlib] type ReplayRecLogPrintFn* = proc(s: seq[string]) {.gcsafe, raises: [].} diff --git a/tools/syncer/replay/replay_reader/reader_gunzip.nim b/tools/syncer/replay/replay_reader/reader_gunzip.nim deleted file mode 100644 index d1c4cd6dad..0000000000 --- a/tools/syncer/replay/replay_reader/reader_gunzip.nim +++ /dev/null @@ -1,352 +0,0 @@ -# Nimbus -# Copyright (c) 2025 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or -# http://www.apache.org/licenses/LICENSE-2.0) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or -# http://opensource.org/licenses/MIT) -# at your option. This file may not be copied, modified, or distributed except -# according to those terms. - -## Incremental unzip based on `Stream` input (derived from -## `test/replay/unzip.nim`.) - -{.push raises:[].} - -import - std/[os, streams, strutils], - pkg/[chronicles, results, zlib] - -logScope: - topics = "replay gunzip" - -const - DontQuit = low(int) - ## To be used with `onCloseException()` - - ReadBufLen = 2048 - ## Size of data chunks to be read from stream. - -type - GUnzipStatus* = tuple - zError: ZError - info: string - - GUnzipRef* = ref object - mz: ZStream ## Gzip sub-system - nextInBuf: array[4096,char] ## Input buffer for gzip `mz.next_in` - nextOutBuf: array[2048,char] ## Output buffer for gzip `mz.next_out` - - inStream: Stream ## Input stream - inName: string ## Registered gzip file name (if any) - outDoneOK: bool ## Gzip/inflate stream end indicator - - lnCache: string ## Input line buffer, used by `nextLine` - lnError: GUnzipStatus ## Last error cache for line iterator - -# ------------------------------------------------------------------------------ -# Private helpers -# ------------------------------------------------------------------------------ - -template onException( - info: static[string]; - quitCode: static[int]; - code: untyped) = - try: - code - except CatchableError as e: - const blurb = info & "Gunzip exception" - when quitCode == DontQuit: - error blurb, error=($e.name), msg=e.msg - else: - fatal blurb & " -- STOP", error=($e.name), msg=e.msg - quit(quitCode) - -proc extractLine(gz: GUnzipRef; start: int): Opt[string] = - ## Extract the first string from line buffer. Any newline characters at - ## the line end will be stripped. The argument `start` is the position - ## where to start searching for the `\n` character. - ## - # Find `\n` in the buffer if there is any - if gz.lnCache.len <= start: - return err() - var nlPos = gz.lnCache.find(char('\n'), start) - if nlPos < 0: - return err() - - # Assemble return value - var line = gz.lnCache.toOpenArray(0,nlPos-1).substr() - line.stripLineEnd - - # Update line cache - gz.lnCache = if gz.lnCache.len <= nlPos + 1: "" - else: gz.lnCache.toOpenArray(nlPos+1, gz.lnCache.len-1).substr() - - # Done - ok(move line) - -# ------------------------------------------------------------------------------ -# Private inflate function -# ------------------------------------------------------------------------------ - -proc loadInput(gz: GUnzipRef; data: openArray[char]): string = - ## Fill input chache for `explode()` and return the overflow. - ## - # Gzip input buffer general layout - # :: - # | <---------------- nextInBuf.len -------------------------> | - # |--------------------+--------------------+------------------| - # | <--- total_in ---> | <--- avail_in ---> | <--- unused ---> | - # | | - # nextInBuf next_in - # - # to be initialised as - # :: - # | <---------------- nextInBuf.len -------------------------> | - # |--------------------------+---------------------------------| - # | <------ avail_in ------> | <----------- unused ----------> | - # | - # nextInBuf - # next_in - # - var buffer = newSeqUninit[char](gz.mz.avail_in.int + data.len) - - # Collect remaining data first - if 0 < gz.mz.avail_in: - (addr buffer[0]).copyMem(gz.mz.next_in, gz.mz.avail_in) - - # Append new data - (addr buffer[gz.mz.avail_in]).copyMem(addr data[0], data.len) - - # Realign gzip input buffer and fill as much as possible from `buffer[]` - gz.mz.next_in = cast[ptr uint8](addr gz.nextInBuf[0]) - gz.mz.total_in = 0 - - if gz.nextInBuf.len < buffer.len: - # The `buffer[]` does not fully fit into `nextInBuf[]`. - (addr gz.nextInBuf).copyMem(addr buffer[0], gz.nextInBuf.len) - gz.mz.avail_in = gz.nextInBuf.len.cuint - # Return overflow - return buffer.toOpenArray(gz.nextInBuf.len, buffer.len-1).substr() - - (addr gz.nextInBuf).copyMem(addr buffer[0], buffer.len) - gz.mz.avail_in = buffer.len.cuint - return "" - - -proc explodeImpl(gz: GUnzipRef; overflow: var string): Result[string,ZError] = - ## Implement `explode()` processing. - ## - if gz.outDoneOK: - return err(Z_STREAM_END) - - var - outData = "" - zRes = Z_STREAM_END - - while not gz.outDoneOK and 0 < gz.mz.avail_in: - gz.mz.next_out = cast[ptr uint8](addr gz.nextOutBuf[0]) - gz.mz.avail_out = gz.nextOutBuf.len.cuint - gz.mz.total_out = 0 - - # Save input state to compare with, below - let availIn = gz.mz.avail_in - - # Deflate current block `next_in[]` => `next_out[]` - zRes = gz.mz.inflate(Z_SYNC_FLUSH) - if zRes == Z_STREAM_END: - gz.outDoneOK = true - zRes = gz.mz.inflateEnd() - # Dont't stop here, `outData` needs to be assigned - if zRes != Z_OK: - break - - # Append processed data - if 0 < gz.mz.total_out: - outData &= gz.nextOutBuf.toOpenArray(0, gz.mz.total_out-1).substr() - - if gz.outDoneOK: - break - - if gz.mz.avail_in < availIn: - # Re-load overflow - if 0 < overflow.len: - overflow = gz.loadInput overflow.toOpenArray(0, overflow.len-1) - - elif gz.mz.avail_out == gz.nextOutBuf.len.cuint: - # Stop unless state change - zRes = Z_BUF_ERROR - break - - if zRes != Z_OK: - return err(zRes) - - ok(outData) - - -proc explode(gz: GUnzipRef; data: openArray[char]): Result[string,ZError] = - ## Inflate the `data[]` argument together with the rest from the previous - ## inflation action and returns the inflated value (and possibly the input - ## buffer overflow.) - ## - var overflow = gz.loadInput data - gz.explodeImpl(overflow) - -proc explode(gz: GUnzipRef): Result[string,ZError] = - ## Variant of `explode()` which clears the rest of the input buffer. - ## - var overflow = "" - gz.explodeImpl(overflow) - -# ------------------------------------------------------------------------------ -# Public -# ------------------------------------------------------------------------------ - -proc init*(T: type GUnzipRef; inStream: Stream): Result[T,GUnzipStatus] = - ## Set up gUnzip filter and prepare for deflating. - ## - const info = "GUnzipRef.init(): " - var chunk: array[ReadBufLen,char] - - # Read header buffer from stream - var chunkLen: int - info.onException(DontQuit): - chunkLen = inStream.readData(addr chunk, chunk.len) - - # Parse GZIP header (RFC 1952) - if chunkLen < 18: - return err((Z_STREAM_ERROR, "Stream too short")) - if (chunk[0].ord != 0x1f or # magic number - chunk[1].ord != 0x8b or # magic number - chunk[2].ord != 0x08) or # deflate - (chunk[3].ord and 0xf7) != 0: # unsupported flags - return err((Z_STREAM_ERROR, "Wrong magic or flags")) - - # Set start of payload - var - pylStart = 10 - inName = "" - if (chunk[3].ord and 8) == 8: # FNAME - var endPos = chunk.toOpenArray(pylStart, chunkLen-1).find char(0) - if endPos < 0: - return err((Z_STREAM_ERROR, "Advertised but missing file name")) - endPos += pylStart # need absolute position in `chunk[]` - inName = chunk.toOpenArray(pylStart, endPos-1).substr() - pylStart = endPos + 1 - - # Initialise descriptor - let gz = GUnzipRef( - inStream: inStream, - inName: inName) - - # Initialise `zlib` and return - let gRc = gz.mz.inflateInit2(Z_RAW_DEFLATE) - if gRc != Z_OK: - return err((gRc,"Zlib init error")) - - # Store unused buffer data for the first read - gz.mz.avail_in = (chunk.len - pylStart).cuint - (addr gz.nextInBuf).copyMem(addr chunk[pylStart], gz.mz.avail_in.int) - gz.mz.next_in = cast[ptr uint8](addr gz.nextInBuf[0]) - gz.mz.total_in = 0 # i.e. left aligned data - - ok(gz) - -proc name*(gz: GUnzipRef): string = - ## Getter: returns registered name (if any) - gz.inName - - -proc nextChunk*(gz: GUnzipRef): Result[string,GUnzipStatus] = - ## Fetch next unzipped data chunk, return and empty string if input - ## is exhausted. - ## - const info = "nextChunk(GUnzipRef): " - - if gz.outDoneOK: - return err((Z_STREAM_END,"")) - - var - chunk: array[ReadBufLen,char] - chunkLen = 0 - data = "" - - info.onException(DontQuit): - chunkLen = gz.inStream.readData(addr chunk, chunk.len) - - if 0 < chunkLen: - data = gz.explode(chunk.toOpenArray(0, chunkLen-1)).valueOr: - return err((error,"Decoding error")) - else: - var atEnd = false - info.onException(DontQuit): - atEnd = gz.inStream.atEnd() - if atEnd: - data = gz.explode().valueOr: - return err((error,"Decoding error")) - else: - return err((Z_STREAM_ERROR, "Stream too short")) - - return ok(move data) - - -proc nextLine*(gz: GUnzipRef): Result[string,GUnzipStatus] = - ## If the gzip stream is expected to contain text data only it can be - ## retrieved line wise. The line string returned has the EOL characters - ## stripped. - ## - ## If all lines are exhausted, the error code `Z_STREAM_END` is returned. - ## - # Check whether there is a full line in the buffer, already - gz.extractLine(0).isErrOr: - return ok(value) - - # Load next chunk(s) into line cache and (try to) extract a complete line. - while not gz.outDoneOK: - let chunk = gz.nextChunk().valueOr: - if gz.outDoneOK: - break - return err(error) - - # Append data chunk to line cache and (try to) extract a line. - let inLen = gz.lnCache.len - gz.lnCache &= chunk - gz.extractLine(inLen).isErrOr: - return ok(value) - # continue - - # Last line (may be partial) - if 0 < gz.lnCache.len: - var line = gz.lnCache - line.stripLineEnd - gz.lnCache = "" - return ok(move line) - - err((Z_STREAM_END,"")) - - -proc atEnd*(gz: GUnzipRef): bool = - ## Returns `true` if data are exhausted. - gz.outDoneOK and gz.lnCache.len == 0 - - -iterator line*(gz: GUnzipRef): string = - ## Iterate over `nextLine()` until the input stream is exhausted. - gz.lnError = (Z_OK, "") - while true: - var ln = gz.nextLine().valueOr: - gz.lnError = error - break - yield ln - -func lineStatus*(gz: GUnzipRef): GUnzipStatus = - ## Error (or no-error) status after the `line()` iterator has terminated. - gz.lnError - -func lineStatusOk*(gz: GUnzipRef): bool = - ## Returns `true` if the `line()` iterator has terminated without error. - gz.lnError[0] in {Z_OK, Z_STREAM_END} - -# ------------------------------------------------------------------------------ -# End -# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_init.nim b/tools/syncer/replay/replay_reader/reader_init.nim index 9469676781..29dcfeedc8 100644 --- a/tools/syncer/replay/replay_reader/reader_init.nim +++ b/tools/syncer/replay/replay_reader/reader_init.nim @@ -14,9 +14,8 @@ import std/[endians, os, streams, strutils], - pkg/[chronicles, eth/common], - ../replay_desc, - ./reader_gunzip + pkg/[chronicles, eth/common, zlib], + ../replay_desc logScope: topics = "replay reader" @@ -72,27 +71,29 @@ proc getFileSignature(strm: Stream): (FileSignature,uint16) = # ------------------------------------------------------------------------------ proc plainReadLine(rp: ReplayReaderRef): Opt[string] = - const info = "plainReadLine(ReplayRef): " + const info = "plainReadLine(): " info.onException(DontQuit): if not rp.inStream.atEnd(): return ok(rp.inStream.readLine) err() proc plainAtEnd(rp: ReplayReaderRef): bool = - const info = "plainAtEnd(ReplayRef): " + const info = "plainAtEnd(): " info.onException(DontQuit): return rp.inStream.atEnd() true proc gUnzipReadLine(rp: ReplayReaderRef): Opt[string] = - const info = "gzipReadLine(ReplayRef): " - var ln = rp.gzFilter.nextLine().valueOr: + const info = "gUnzipReadLine(): " + var rc = Result[string,GUnzipStatus].err((Z_STREAM_ERROR,"")) + info.onException(DontQuit): + rc = rp.gzFilter.nextLine() + if rc.isErr(): if not rp.gzFilter.lineStatusOk(): let err = rp.gzFilter.lineStatus() info info & "GUnzip filter error", zError=err.zError, info=err.info - discard return err() - ok(move ln) + ok(rc.value) proc gUnzipAtEnd(rp: ReplayReaderRef): bool = rp.gzFilter.atEnd() @@ -102,7 +103,7 @@ proc gUnzipAtEnd(rp: ReplayReaderRef): bool = # ------------------------------------------------------------------------------ proc init*(T: type ReplayReaderRef; strm: Stream): T = - const info = "ReplayRef.init(): " + const info = "ReplayReaderRef.init(): " if strm.isNil: fatal info & "Cannot use nil stream for reading -- STOP" @@ -118,9 +119,13 @@ proc init*(T: type ReplayReaderRef; strm: Stream): T = rp.readLine = plainReadLine rp.atEnd = plainAtEnd of Gzip: - rp.gzFilter = GUnzipRef.init(strm).valueOr: + var rc = Result[GUnzipRef,GUnzipStatus].err((Z_STREAM_ERROR,"")) + info.onException(DontQuit): + rc = GUnzipRef.init(strm) + if rc.isErr: fatal info & "Cannot assign gunzip reader -- STOP" quit(QuitFailure) + rp.gzFilter = rc.value rp.readLine = gUnzipReadLine rp.atEnd = gUnzipAtEnd of Unknown: @@ -132,7 +137,7 @@ proc init*(T: type ReplayReaderRef; strm: Stream): T = proc destroy*(rp: ReplayReaderRef) = - const info = "destroy(ReplayRef): " + const info = "destroy(ReplayReaderRef): " info.onException(DontQuit): rp.inStream.flush() diff --git a/tools/syncer/replay/replay_reader/reader_unpack.nim b/tools/syncer/replay/replay_reader/reader_unpack.nim index 653aeccb94..5d77b09913 100644 --- a/tools/syncer/replay/replay_reader/reader_unpack.nim +++ b/tools/syncer/replay/replay_reader/reader_unpack.nim @@ -14,8 +14,7 @@ import std/[net, os, streams, strutils], - pkg/[chronicles, chronos, eth/common, stew/base64, stew/byteutils], - ./reader_gunzip, + pkg/[chronicles, chronos, eth/common, stew/base64, stew/byteutils, zlib], ../replay_desc logScope: From aa216708ad6d476655ea1bd463e87114802e0391 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Wed, 10 Sep 2025 11:18:17 +0100 Subject: [PATCH 12/18] Refactor capture records for JSON format read/write --- tools/syncer/replay/README.md | 42 ++- .../replay/replay_reader/reader_init.nim | 6 +- .../replay/replay_reader/reader_reclog.nim | 6 +- .../replay/replay_reader/reader_unpack.nim | 323 ++++++++++++------ .../replay/replay_runner/runner_desc.nim | 23 ++ .../replay/replay_runner/runner_dispatch.nim | 4 +- .../runner_dispatch/dispatch_helpers.nim | 74 +--- tools/syncer/trace/README.md | 46 +-- tools/syncer/trace/trace_desc.nim | 51 ++- .../trace/trace_setup/setup_helpers.nim | 6 +- .../syncer/trace/trace_setup/setup_write.nim | 139 ++------ 11 files changed, 393 insertions(+), 327 deletions(-) diff --git a/tools/syncer/replay/README.md b/tools/syncer/replay/README.md index e0787d8621..97ca6c3d31 100644 --- a/tools/syncer/replay/README.md +++ b/tools/syncer/replay/README.md @@ -4,31 +4,39 @@ Inspection of Capture Data And Replay Inspection ---------- -Given a (probably gzipped) capture file **(capture)** as a result of -tracing, its content can be visualised via +Given a (probably *gzipped*) capture file **(capture)** as a result of +tracing, its content can be visualised as a space separated list of +selected text fields via ./build/syncer_test_client_inspect --capture-file=(capture) +As the **(capture)** is a list of *JSON* text lines, the *gunzipped* version +can also can be inspected with a text editor (or perusal pager *more* or +*less*.). + Replay ------ -Copy and secure the current database directory **(database)** as **(dbcopy)**, -say. Then start a capture run on the original data base as +Copy the current database directory **(database)** and its recursive content +as **(dbcopy)**, say. Then start a capture session on the original data base +via - ./build/syncer_test_client_replay \ - --datadir=(database) ... -- --capture-file=(capture) + ./build/syncer_test_client_trace \ + --datadir=(database) ... -- --capture-file=(capture) -where **(capture)** will contain all the data for the replay. This file can -bebome quite big (e.g. 30GiB for the last 120k blocks synchronised on -*mainnet*) but can be gzipped after the capture run was stopped. +where **...** stands for all other options that might be useful for running +an execution layer session and **(capture)** will collect all the data needed +for replay. This file can become quite huge. It should be *gzipped* after the +capture run has finished and the *gzipped* version used, instead. -Monitor the capture run so it can be stopped at an an appropriate state using -metrics or logs. With the above command line argumants, only the next sync -session is logged ranging from the activation message (when *Activating syncer* -is logged) up intil the suspend message (when *Suspending syncer* is logged.) +Monitor the capture run so it can be stopped at an appropriate state using +metrics or logs. With the above command line arguments, only a single sync +session is logged ranging from the first activation message (when *"Activating +syncer"* is logged) up until the suspend message (when *"Suspending syncer"* +is logged.) -Now, the captured run can be replayed on the secured database copy -**(dbcopy)** with the (probably gzipped) **(capture)** file via +Now, the captured session can be replayed on the secured database copy +**(dbcopy)** with the (probably *gzipped*) **(capture)** file via ./build/syncer_test_client_replay \ --datadir=(dbcopy) ... -- --capture-file=(capture) @@ -36,5 +44,5 @@ Now, the captured run can be replayed on the secured database copy where ihe additional arguments **...** of either command above need not be the same. -Note that you need another copy of **(database)** if you need to re-exec the -latter command line statement. +Note that you need another copy of the original **(database)** if you need to +re-exec the latter command line statement for another replay. diff --git a/tools/syncer/replay/replay_reader/reader_init.nim b/tools/syncer/replay/replay_reader/reader_init.nim index 29dcfeedc8..9d2299843a 100644 --- a/tools/syncer/replay/replay_reader/reader_init.nim +++ b/tools/syncer/replay/replay_reader/reader_init.nim @@ -59,9 +59,11 @@ proc getFileSignature(strm: Stream): (FileSignature,uint16) = if u16 == 0x1f8b'u16: return (Gzip,u16) - # Ascii signature: /[0-9A-Z] / + # Ascii signature: /{"/ or /#[0-9a-zA-Z ]/ let (c0, c1) = (char(u16 shr 8), char(u16.uint8)) - if (c0.isDigit or c0.isUpperAscii or c0 == '#') and (c1 in {' ','\r','\n'}): + if c0 == '{' and c1 == '"': + return (Plain,u16) + if c0 == '#' and (c1.isAlphaNumeric or c1.isSpaceAscii): return (Plain,u16) (Unknown,u16) diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim index d2fc7d999b..2562f7f029 100644 --- a/tools/syncer/replay/replay_reader/reader_reclog.nim +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -303,8 +303,11 @@ func recLogToStrEnd*(n: int): seq[string] = @[".", $n] proc recLogToStrList*(pyl: ReplayPayloadRef; lnr = 0): seq[string] = + ## Convert the internal capture object argument `pyl` to a list of + ## printable strings. + ## case pyl.recType: - of TrtOops: + of TraceRecType(0): lnr.toStrOops() of TrtVersionInfo: @@ -341,7 +344,6 @@ proc recLogToStrList*(pyl: ReplayPayloadRef; lnr = 0): seq[string] = lnr.toStrSeq(pyl.ReplayFetchBodies.data) of TrtSyncBodies: lnr.toStrSeq(pyl.ReplaySyncBodies.data) - of TrtImportBlock: lnr.toStrSeq(pyl.ReplayImportBlock.data) of TrtSyncBlock: diff --git a/tools/syncer/replay/replay_reader/reader_unpack.nim b/tools/syncer/replay/replay_reader/reader_unpack.nim index 5d77b09913..37b5bff714 100644 --- a/tools/syncer/replay/replay_reader/reader_unpack.nim +++ b/tools/syncer/replay/replay_reader/reader_unpack.nim @@ -13,33 +13,37 @@ {.push raises:[].} import - std/[net, os, streams, strutils], - pkg/[chronicles, chronos, eth/common, stew/base64, stew/byteutils, zlib], + std/[net, strutils], + pkg/[chronicles, chronos, eth/common, results, stew/interval_set], + pkg/json_serialization/pkg/results, + pkg/eth/common/eth_types_json_serialization, ../replay_desc logScope: topics = "replay reader" +type + JsonKind = object + ## For extracting record type only (use with flavor: `SingleField`) + kind: TraceRecType + + BnPair = object + ## For parsing `BnRange` + least: BlockNumber + last: BlockNumber + const DontQuit = low(int) ## To be used with `onCloseException()` # ------------------------------------------------------------------------------ -# Private mixin helpers for RLP decoder +# Private JSON config # ------------------------------------------------------------------------------ -proc read(rlp: var Rlp; T: type Hash): T {.raises:[RlpError].} = - when sizeof(T) != sizeof(uint): - # `castToUnsigned()` is defined in `std/private/bitops_utils` and - # included by `std/bitops` but not exported (as of nim 2.2.4) - {.error: "Expected that Hash is based on int".} - Hash(int(cast[int64](rlp.read(uint64)))) - -proc read(rlp: var Rlp; T: type chronos.Duration): T {.raises:[RlpError].} = - chronos.nanoseconds(cast[int64](rlp.read(uint64))) +createJsonFlavor SingleField, + requireAllFields = false -proc read(rlp: var Rlp; T: type Port): T {.raises:[RlpError].} = - Port(rlp.read(uint16)) +JsonKind.useDefaultSerializationIn SingleField # ------------------------------------------------------------------------------ # Private helpers @@ -52,127 +56,244 @@ template onException( try: code except CatchableError as e: - const blurb = info & "Replay stream reader exception" + const blurb = info & ": Replay stream reader exception" when quitCode == DontQuit: error blurb, error=($e.name), msg=e.msg else: fatal blurb & " -- STOP", error=($e.name), msg=e.msg quit(quitCode) -proc init(T: type; blob: string; recType: static[TraceRecType]; U: type): T = - const info = "init(" & $recType & "): " - var rlpBlob: seq[byte] - info.onException(DontQuit): - rlpBlob = Base64.decode(blob) - return T( - recType: recType, - data: rlp.decode(rlpBlob, U)) +func fromHex(c: char): int = + case c + of '0'..'9': ord(c) - ord('0') + of 'a'..'f': ord(c) - ord('a') + 10 + of 'A'..'F': ord(c) - ord('A') + 10 + else: -1 + + +proc toIp4Address(s: string): Opt[IpAddress] = + ## Parse IPv4 dotted address string + ## + # Make sure that `nibbles.len` == 4 + let dgts = s.split('.') + if dgts.len != 4: + return err() + + var ip4 = IpAddress(family: IpAddressFamily.IPv4) + for n in 0 .. 3: + "toIp4Address()".onException(DontQuit): + ip4.address_v4[n] = dgts[n].parseUInt().uint8 + continue + return err() + ok(move ip4) + + +proc toIp6Address(s: string): Opt[IpAddress] = + ## Parse IPv6 address string + ## + # Make sure that `nibbles.len` == 8 + var xDgts = s.split(':') + if xDgts.len < 3 or 8 < xDgts.len: + return err() + # Take care of shortcuts like "::ffff:6366:d1" or "::1" + var (start, pfxLen) = (0, 0) + if xDgts.len < 8: + # A shortcut for missing zeros must start with "::" + if xDgts[0].len == 0 and xDgts[1].len == 0: + (start, pfxLen) = (2, 8 - xDgts.len) + else: + return err() + + var ip6 = IpAddress(family: IpAddressFamily.IPv6) + for n in start ..< xDgts.len: + if xDgts[n].len != 0: + "toIp6Address()".onException(DontQuit): + let + u16 = xDgts[n].parseHexInt().uint16 + pos = 2 * (pfxLen + n) + ip6.address_v6[pos] = (u16 shr 8).uint8 + ip6.address_v6[pos+1] = (u16 and 255).uint8 + continue + return err() + ok(move ip6) # ------------------------------------------------------------------------------ -# Public record decoder functions +# Private JSON mixin helpers for decoder # ------------------------------------------------------------------------------ -proc unpack*(line: string): ReplayPayloadRef = - if line.len < 3: - return ReplayPayloadRef(nil) - - var recType: TraceRecType - if line[0].isDigit: - let n = line[0].ord - '0'.ord - if high(TraceRecType).ord < n: - return ReplayPayloadRef(nil) - recType = TraceRecType(n) - - elif line[0].isUpperAscii: - let n = line[0].ord - 'A'.ord + 10 - if high(TraceRecType).ord < n: - return ReplayPayloadRef(nil) - recType = TraceRecType(n) - +proc readValue( + r: var JsonReader; + v: var chronos.Duration; + ) {.raises: [IOError, SerializationError].} = + let kind = r.tokKind + case kind: + of JsonValueKind.Number: + var u64: uint64 + r.readValue(u64) + v = nanoseconds(cast[int64](u64)) + else: + r.raiseUnexpectedValue("Invalid Duiration value type: " & $kind) + +proc readValue( + r: var JsonReader; + v: var IpAddress; + ) {.raises: [IOError, SerializationError].} = + let kind = r.tokKind + case kind: + of JsonValueKind.String: + var ipString: string + r.readValue(ipString) + if 0 <= ipString.find('.'): + v = ipString.toIp4Address.valueOr: + r.raiseUnexpectedValue("Invalid IPv4 address value: " & $ipString) + else: + v = ipString.toIp6Address.valueOr: + r.raiseUnexpectedValue("Invalid IPv6 address value: " & $ipString) + else: + r.raiseUnexpectedValue("Invalid IP address value type: " & $kind) + +proc readValue( + r: var JsonReader; + v: var Port; + ) {.raises: [IOError, SerializationError].} = + let kind = r.tokKind + case kind: + of JsonValueKind.Number: + var u64: uint64 + r.readValue(u64) + if 0xffffu < u64: + r.raiseUnexpectedValue("Invalid Port value: " & $u64) + v = Port(cast[uint16](u64)) else: - return ReplayPayloadRef(nil) + r.raiseUnexpectedValue("Invalid Port value type: " & $kind) + +proc readValue( + r: var JsonReader; + v: var UInt256; + ) {.raises: [IOError, SerializationError].} = + ## Modified copy from `common.chain_config.nim` needed for parsing + ## a `NetworkId` type value. + ## + var (accu, ok) = (0.u256, true) + let kind = r.tokKind + case kind: + of JsonValueKind.Number: + try: + r.customIntValueIt: + accu = accu * 10 + it.u256 + except CatchableError: + ok = false + of JsonValueKind.String: + try: + var (sLen, base) = (0, 10) + r.customStringValueIt: + if ok: + var num = it.fromHex + if base <= num: + ok = false # cannot be larger than base + elif sLen < 2: + if 0 <= num: + accu = accu * base.u256 + num.u256 + elif sLen == 1 and it in {'x', 'X'}: + base = 16 # handle "0x" prefix + else: + ok = false + sLen.inc + elif num < 0: + ok = false # not a hex digit + elif base == 10: + accu = accu * 10 + num.u256 + else: + accu = accu * 16 + num.u256 + except CatchableError: + r.raiseUnexpectedValue("UInt256 string parse error") + else: + r.raiseUnexpectedValue("Invalid UInt256 value type: " & $kind & + " (expect int or hex/int string)") + if not ok: + r.raiseUnexpectedValue("UInt256 parse error") + v = accu + +proc readValue( + r: var JsonReader; + v: var BnRange; + ) {.raises: [IOError, SerializationError].} = + let kind = r.tokKind + case kind: + of JsonValueKind.Object: + var bnPair: BnPair + r.readValue(bnPair) + v = BnRange.new(bnPair.least, bnPair.last) + else: + r.raiseUnexpectedValue("Invalid BnRange value type: " & $kind) - let data = line.substr(2, line.len-1) - case recType: - of TrtOops: - return ReplayPayloadRef( - recType: TrtOops) +# ------------------------------------------------------------------------------ +# Private functions +# ------------------------------------------------------------------------------ - of TrtVersionInfo: - return ReplayVersionInfo.init( - data, TrtVersionInfo, TraceVersionInfo) +proc getRecType(s: string; info: static[string]): TraceRecType = + (info & ".getRecType()").onException(DontQuit): + let j = SingleField.decode(s, JsonKind) + return j.kind + TraceRecType(0) - # ------------------ +proc init(T: type; s: string; info: static[string]): T = + (info & ".init()").onException(DontQuit): + var rec = Json.decode(s, JTraceRecord[typeof result.data]) + return T(recType: rec.kind, data: rec.bag) + T(nil) - of TrtSyncActvFailed: - return ReplaySyncActvFailed.init( - data, TrtSyncActvFailed, TraceSyncActvFailed) +# ------------------------------------------------------------------------------ +# Public record decoder functions +# ------------------------------------------------------------------------------ - of TrtSyncActivated: - return ReplaySyncActivated.init( - data, TrtSyncActivated, TraceSyncActivated) +proc unpack*(line: string): ReplayPayloadRef = + ## Decode a JSON string argument `line` and convert it to an internal object. + ## The function always returns a non-nil value. + ## + const info = "unpack" + + case line.getRecType(info): + of TraceRecType(0): + return ReplayPayloadRef(recType: TraceRecType(0)) + of TrtVersionInfo: + return ReplayVersionInfo.init(line, info) + of TrtSyncActvFailed: + return ReplaySyncActvFailed.init(line, info) + of TrtSyncActivated: + return ReplaySyncActivated.init(line, info) of TrtSyncHibernated: - return ReplaySyncHibernated.init( - data, TrtSyncHibernated, TraceSyncHibernated) - - # ------------------ + return ReplaySyncHibernated.init(line, info) of TrtSchedDaemonBegin: - return ReplaySchedDaemonBegin.init( - data, TrtSchedDaemonBegin, TraceSchedDaemonBegin) - + return ReplaySchedDaemonBegin.init(line, info) of TrtSchedDaemonEnd: - return ReplaySchedDaemonEnd.init( - data, TrtSchedDaemonEnd, TraceSchedDaemonEnd) - + return ReplaySchedDaemonEnd.init(line, info) of TrtSchedStart: - return ReplaySchedStart.init( - data, TrtSchedStart, TraceSchedStart) - + return ReplaySchedStart.init(line, info) of TrtSchedStop: - return ReplaySchedStop.init( - data, TrtSchedStop, TraceSchedStop) - + return ReplaySchedStop.init(line, info) of TrtSchedPool: - return ReplaySchedPool.init( - data, TrtSchedPool, TraceSchedPool) - + return ReplaySchedPool.init(line, info) of TrtSchedPeerBegin: - return ReplaySchedPeerBegin.init( - data, TrtSchedPeerBegin, TraceSchedPeerBegin) - + return ReplaySchedPeerBegin.init(line, info) of TrtSchedPeerEnd: - return ReplaySchedPeerEnd.init( - data, TrtSchedPeerEnd, TraceSchedPeerEnd) - - # ------------------ + return ReplaySchedPeerEnd.init(line, info) of TrtFetchHeaders: - return ReplayFetchHeaders.init( - data, TrtFetchHeaders, TraceFetchHeaders) - + return ReplayFetchHeaders.init(line, info) of TrtSyncHeaders: - return ReplaySyncHeaders.init( - data, TrtSyncHeaders, TraceSyncHeaders) - + return ReplaySyncHeaders.init(line, info) of TrtFetchBodies: - return ReplayFetchBodies.init( - data, TrtFetchBodies, TraceFetchBodies) - + return ReplayFetchBodies.init(line, info) of TrtSyncBodies: - return ReplaySyncBodies.init( - data, TrtSyncBodies, TraceSyncBodies) - - + return ReplaySyncBodies.init(line, info) of TrtImportBlock: - return ReplayImportBlock.init( - data, TrtImportBlock, TraceImportBlock) - + return ReplayImportBlock.init(line, info) of TrtSyncBlock: - return ReplaySyncBlock.init( - data, TrtSyncBlock, TraceSyncBlock) + return ReplaySyncBlock.init(line, info) # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/replay/replay_runner/runner_desc.nim b/tools/syncer/replay/replay_runner/runner_desc.nim index 51ebfb92b6..e23aa3f459 100644 --- a/tools/syncer/replay/replay_runner/runner_desc.nim +++ b/tools/syncer/replay/replay_runner/runner_desc.nim @@ -113,4 +113,27 @@ type instrNumber*: uint ## Instruction counter fakeImport*: bool ## No database import if `true` +# ------------------------------------------------------------------------------ +# Public helpers +# ------------------------------------------------------------------------------ + +template toReplayMsgType*(trc: type): untyped = + ## Derive replay record type from trace capture record type + when trc is TraceFetchHeaders: + ReplayFetchHeadersMsgRef + elif trc is TraceSyncHeaders: + ReplaySyncHeadersMsgRef + elif trc is TraceFetchBodies: + ReplayFetchBodiesMsgRef + elif trc is TraceSyncBodies: + ReplaySyncBodiesMsgRef + elif trc is TraceImportBlock: + ReplayImportBlockMsgRef + elif trc is TraceSyncBlock: + ReplaySyncBlockMsgRef + else: + {.error: "Unsupported trace record type".} + +# ------------------------------------------------------------------------------ # End +# ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch.nim b/tools/syncer/replay/replay_runner/runner_dispatch.nim index ed58511bac..9bc920302b 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch.nim @@ -30,7 +30,7 @@ proc dispatch*( run: ReplayRunnerRef; pyl: ReplayPayloadRef; ) {.async: (raises: []).} = - ## Execure next instruction + ## Execute the internal capture object argument `pyl` as an instruction. ## run.instrNumber.inc @@ -38,7 +38,7 @@ proc dispatch*( nBuddies=run.peers.len, nDaemons=(if run.daemon.isNil: 0 else: 1) case pyl.recType: - of TrtOops: + of TraceRecType(0): warn "dispatch(): Oops, unexpected void record", n=run.instrNumber of TrtVersionInfo: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim index f8aa5dde03..daedad385e 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim @@ -34,13 +34,11 @@ type ReplayInstance = ReplayDaemonRef | ReplayBuddyRef - SubInstrType = TraceFetchHeaders | TraceSyncHeaders | - TraceFetchBodies | TraceSyncBodies | - TraceImportBlock | TraceSyncBlock - - InstrType = TraceSchedDaemonBegin | TraceSchedDaemonEnd | - TraceSchedPeerBegin | TraceSchedPeerEnd | - SubInstrType + InstrType = TraceSchedDaemonBegin | TraceSchedDaemonEnd | + TraceSchedPeerBegin | TraceSchedPeerEnd | + TraceFetchHeaders | TraceSyncHeaders | + TraceFetchBodies | TraceSyncBodies | + TraceImportBlock | TraceSyncBlock # ------------------------------------------------------------------------------ # Private helper(s) @@ -111,16 +109,13 @@ template waitForConditionImpl( func syncedEnvCondImpl( desc: ReplayInstance; - instr: InstrType; + instr: TraceRecBase; info: static[string]; ): bool = ## Condition function for `waitForConditionImpl()` for synchronising state. ## let ctx = desc.run.ctx - #if serial != run.instrNumber: - # return false - if instr.hdrUnprChunks != ctx.hdr.unprocessed.chunks().uint: return false if 0 < instr.hdrUnprChunks: @@ -633,7 +628,7 @@ template whenProcessFinished*( template pushInstr*( desc: ReplayInstance; - instr: SubInstrType; + instr: untyped; info: static[string]; ): ReplayWaitResult = ## Async/template @@ -643,26 +638,8 @@ template pushInstr*( ## var bodyRc = ReplayWaitResult.ok() block: - when instr is TraceFetchHeaders: - type T = ReplayFetchHeadersMsgRef - const dataType {.inject.} = TrtFetchHeaders - elif instr is TraceSyncHeaders: - type T = ReplaySyncHeadersMsgRef - const dataType {.inject.} = TrtSyncHeaders - - elif instr is TraceFetchBodies: - type T = ReplayFetchBodiesMsgRef - const dataType {.inject.} = TrtFetchBodies - elif instr is TraceSyncBodies: - type T = ReplaySyncBodiesMsgRef - const dataType {.inject.} = TrtSyncBodies - - elif instr is TraceImportBlock: - type T = ReplayImportBlockMsgRef - const dataType {.inject.} = TrtImportBlock - elif instr is TraceSyncBlock: - type T = ReplaySyncBlockMsgRef - const dataType {.inject.} = TrtSyncBlock + const dataType {.inject.} = (typeof instr).toTraceRecType + type M = (typeof instr).toReplayMsgType # Verify that the stage is based on a proper environment doAssert desc.frameID != 0 # this is not `instr.frameID` @@ -676,7 +653,7 @@ template pushInstr*( doAssert desc.message.isNil # Stage/push session data - desc.message = T( + desc.message = M( recType: dataType, instr: instr) @@ -713,7 +690,7 @@ template pushInstr*( template withInstr*( desc: ReplayInstance; - I: type SubInstrType; + I: type; # `instr` type info: static[string]; code: untyped; ) = @@ -725,31 +702,14 @@ template withInstr*( ## * `iError` -- error data, initialised if `instr.isAvailable()` is `false` ## block: - when I is TraceFetchHeaders: - const dataType {.inject.} = TrtFetchHeaders - type M = ReplayFetchHeadersMsgRef - const ignLatestNum = false - elif I is TraceSyncHeaders: - const dataType {.inject.} = TrtSyncHeaders - type M = ReplaySyncHeadersMsgRef - const ignLatestNum = false + const dataType {.inject.} = I.toTraceRecType + type M = I.toReplayMsgType - elif I is TraceFetchBodies: - const dataType {.inject.} = TrtFetchBodies - type M = ReplayFetchBodiesMsgRef - const ignLatestNum = true # relax, de-noise - elif I is TraceSyncBodies: - const dataType {.inject.} = TrtSyncBodies - type M = ReplaySyncBodiesMsgRef + when I is TraceFetchBodies or + I is TraceSyncBodies or + I is TraceImportBlock: const ignLatestNum = true # relax, de-noise - - elif I is TraceImportBlock: - const dataType {.inject.} = TrtImportBlock - type M = ReplayImportBlockMsgRef - const ignLatestNum = true # relax, de-noise - elif I is TraceSyncBlock: - const dataType {.inject.} = TrtSyncBlock - type M = ReplaySyncBlockMsgRef + else: const ignLatestNum = false let diff --git a/tools/syncer/trace/README.md b/tools/syncer/trace/README.md index 8eeaf87fce..4a4b7e2e65 100644 --- a/tools/syncer/trace/README.md +++ b/tools/syncer/trace/README.md @@ -1,9 +1,8 @@ Beacon sync tracer ================== -For the *nimbus_execution_client* binary, data from a syncer sessions can -be captured into a file **(capture)** along with system state information -via +For the execution layer binary, data from a syncer sessions can be captured +into a file **(capture)** along with system state information via ./build/syncer_test_client_trace ... -- --capture-file=(capture) @@ -11,36 +10,25 @@ where **...** stands for all other options that might be useful for running an execution layer session. The capture file **(capture)** will hold enough data for replaying the -*nimbus_execution_client* session(s). +execution layer session(s). -With the command line option *\-\-debug-beacon-sync-trace-file=***(capture)** -for the *nimbus_execution_client* binary, data from the syncer sessions will -be dumped into the argument file **(capture)** along with system state -information. +With the command line option *\-\-capture-file-file=***(capture)** +for the *syncer_test_client_trace* binary, data from the syncer sessions +will be written to the argument file named **(capture)** along with system +state information. The file **(capture)** will hold enough data for +replaying the session(s) with the *syncer_test_client_replay* binary. -The capture file **(capture)** will hold enough data for replaying the -*nimbus_execution_client* session(s). +Both binary *syncer_test_client_trace* and *syncer_test_client_replay* are +extensions of the standard *nimbus_execution_client* binary. By default, the captured syncer session starts with the first syncer activation -(when *Activating syncer* is logged) and ends when the syncer is suspended -(when *Suspending syncer* is logged.) +(when *"Activating syncer"* is logged) and ends when the syncer is suspended +(when *"Suspending syncer"* is logged.) The trace file **(capture)** is organised as an ASCII text file, each line -consists of a data capture record. The line format is - - - -where the ** is a single alphanumeric letter, and ** -is a base64 representation of an rlp-encoded data capture structure. - -By nature of the base64 representation, the size of the trace data is about -four times the data capture which leads to huge files, e.g. some 30GiB for the -last 120k blocks synchronised on *mainnet*. - -The file with the captured data may be gzipped after the dump finished which -reduces ths size roughly to 1/3. So altogether in its gzipped form, the size -of the gzipped trace file is about 4/3 of the capured data (mainly downloaded -block headers and bodies.) +consists of a *JSON* encoded data capture record. -The captured data might be further processed (e.g. inspection or replay) in -its gzipped form. +By nature of the *JSON* representation, the size of any capture data file +will be huge. Compressing with *gzip* when finished, the capture file size +can be reduced to less than 20%. The *gzipped* format will also be accepted +by the replay tools. diff --git a/tools/syncer/trace/trace_desc.nim b/tools/syncer/trace/trace_desc.nim index 4c36cead96..f757d4ce07 100644 --- a/tools/syncer/trace/trace_desc.nim +++ b/tools/syncer/trace/trace_desc.nim @@ -28,7 +28,7 @@ export worker_desc const - TraceVersionID* = 20250828 + TraceVersionID* = 20250915 TraceSetupID* = 1 ## Phase 1 layout ID, prepare TraceRunnerID* = 10 ## Phase 2 layout ID, full execution @@ -51,7 +51,7 @@ type # ------------- TraceRecType* = enum - TrtOops = 0 + TrtRecBase = 0 TrtVersionInfo = 1 TrtSyncActvFailed @@ -106,7 +106,6 @@ type nBlkErrors*: uint8 ## 8) # body comm. errors slowPeer*: Hash ## 16) Registered slow peer - TraceVersionInfo* = object of TraceRecBase version*: uint networkId*: NetworkId @@ -195,6 +194,13 @@ type TraceSyncBlock* = object of TraceRecBase ## Environment is captured after the `syncImportBlock()` handler is run. + # ------------- + + JTraceRecord*[T] = object + ## Json writer record format + kind*: TraceRecType + bag*: T + # ------------------------------------------------------------------------------ # Public helpers # ------------------------------------------------------------------------------ @@ -210,6 +216,45 @@ func newSerial*(trc: TraceRef): uint64 = trc.serial.inc trc.serial +func toTraceRecType*(T: type): TraceRecType = + ## Derive capture type from record layout + when T is TraceVersionInfo: + TrtVersionInfo + elif T is TraceSyncActvFailed: + TrtSyncActvFailed + elif T is TraceSyncActivated: + TrtSyncActivated + elif T is TraceSyncHibernated: + TrtSyncHibernated + elif T is TraceSchedDaemonBegin: + TrtSchedDaemonBegin + elif T is TraceSchedDaemonEnd: + TrtSchedDaemonEnd + elif T is TraceSchedStart: + TrtSchedStart + elif T is TraceSchedStop: + TrtSchedStop + elif T is TraceSchedPool: + TrtSchedPool + elif T is TraceSchedPeerBegin: + TrtSchedPeerBegin + elif T is TraceSchedPeerEnd: + TrtSchedPeerEnd + elif T is TraceFetchHeaders: + TrtFetchHeaders + elif T is TraceSyncHeaders: + TrtSyncHeaders + elif T is TraceFetchBodies: + TrtFetchBodies + elif T is TraceSyncBodies: + TrtSyncBodies + elif T is TraceImportBlock: + TrtImportBlock + elif T is TraceSyncBlock: + TrtSyncBlock + else: + {.error: "Unsupported trace capture record type".} + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/trace/trace_setup/setup_helpers.nim b/tools/syncer/trace/trace_setup/setup_helpers.nim index f51201f563..13bf5cbfc8 100644 --- a/tools/syncer/trace/trace_setup/setup_helpers.nim +++ b/tools/syncer/trace/trace_setup/setup_helpers.nim @@ -20,7 +20,7 @@ export worker_helpers # ------------------------------------------------------------------------------ -# Public context capture initialisation +# Public initialisers # ------------------------------------------------------------------------------ proc init*(tb: var TraceRecBase; ctx: BeaconCtxRef) = @@ -71,7 +71,9 @@ proc init*(tb: var TraceRecBase; buddy: BeaconBuddyRef) = tb.nHdrErrors = buddy.only.nRespErrors.hdr tb.nBlkErrors = buddy.only.nRespErrors.blk -# -------------- +# ------------------------------------------------------------------------------ +# Public helpers +# ------------------------------------------------------------------------------ func short*(w: Hash): string = w.toHex(8).toLowerAscii # strips leading 8 bytes diff --git a/tools/syncer/trace/trace_setup/setup_write.nim b/tools/syncer/trace/trace_setup/setup_write.nim index 652e5cba27..8577656310 100644 --- a/tools/syncer/trace/trace_setup/setup_write.nim +++ b/tools/syncer/trace/trace_setup/setup_write.nim @@ -11,140 +11,55 @@ {.push raises:[].} import - std/[net, streams, typetraits], - pkg/[chronicles, chronos, eth/common, stew/base64], + std/streams, + pkg/[chronicles, chronos, json_serialization], + pkg/json_serialization/pkg/results as json_results, + pkg/eth/common/eth_types_json_serialization as json_eth_types, ../trace_desc +export + json_eth_types, + json_results, + json_serialization + logScope: topics = "beacon trace" -# ------------------------------------------------------------------------------ -# Private mixin helpers for RLP encoder -# ------------------------------------------------------------------------------ - -proc append(w: var RlpWriter, h: Hash) = - when sizeof(h) != sizeof(uint): - # `castToUnsigned()` is defined in `std/private/bitops_utils` and - # included by `std/bitops` but not exported (as of nim 2.2.4) - {.error: "Expected that Hash is based on int".} - w.append(cast[uint](h).uint64) - -proc append(w: var RlpWriter, d: chronos.Duration) = - w.append(cast[uint64](d.nanoseconds)) - -proc append(w: var RlpWriter, p: Port) = - w.append(distinctBase p) - # ------------------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------------------ -proc toTypeInx(w: TraceRecType): string = - if w.ord < 10: - $w.ord - else: - $chr(w.ord + 'A'.ord - 10) - - -proc toStream( - buddy: BeaconBuddyRef; - trp: TraceRecType; - blob: seq[byte]; - flush = false; - ) = - ## Write tracet data to output stream - let trc = buddy.ctx.trace - if trc.isNil: - debug "Trace output stopped while collecting", - peer=($buddy.peer), recType=trp - else: - try: - trc.outStream.writeLine trp.toTypeInx & " " & Base64.encode(blob) - trc.outStream.flush() - except CatchableError as e: - warn "Error writing trace data", peer=($buddy.peer), recType=trp, - recSize=blob.len, error=($e.name), msg=e.msg - -proc toStream( - ctx: BeaconCtxRef; - trp: TraceRecType; - blob: seq[byte]; - flush = false; - ) = - ## Variant of `toStream()` for `ctx` rather than `buddy` +proc toStream(ctx: BeaconCtxRef; trp: TraceRecType; data: string) = + ## Write tracer data to output stream let trc = ctx.trace if trc.isNil: debug "Trace output stopped while collecting", recType=trp else: try: - trc.outStream.writeLine trp.toTypeInx & " " & Base64.encode(blob) + trc.outStream.writeLine data trc.outStream.flush() except CatchableError as e: warn "Error writing trace data", recType=trp, - recSize=blob.len, error=($e.name), msg=e.msg + recSize=data.len, error=($e.name), msg=e.msg # ------------------------------------------------------------------------------ # Public functions # ------------------------------------------------------------------------------ -proc traceWrite*(ctx: BeaconCtxRef; w: TraceVersionInfo) = - ctx.toStream(TrtVersionInfo, rlp.encode w) - -# ------------- - -proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncActvFailed) = - ctx.toStream(TrtSyncActvFailed, rlp.encode w) - -proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncActivated) = - ctx.toStream(TrtSyncActivated, rlp.encode w) - -proc traceWrite*(ctx: BeaconCtxRef; w: TraceSyncHibernated) = - ctx.toStream(TrtSyncHibernated, rlp.encode w) - -# ------------- - -proc traceWrite*(ctx: BeaconCtxRef; w: TraceSchedDaemonBegin) = - ctx.toStream(TrtSchedDaemonBegin, rlp.encode w) - -proc traceWrite*(ctx: BeaconCtxRef; w: TraceSchedDaemonEnd) = - ctx.toStream(TrtSchedDaemonEnd, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedStart) = - buddy.toStream(TrtSchedStart, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedStop) = - buddy.toStream(TrtSchedStop, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPool) = - buddy.toStream(TrtSchedPool, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPeerBegin) = - buddy.toStream(TrtSchedPeerBegin, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSchedPeerEnd) = - buddy.toStream(TrtSchedPeerEnd, rlp.encode w) - -# ------------- - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceFetchHeaders) = - buddy.toStream(TrtFetchHeaders, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncHeaders) = - buddy.toStream(TrtSyncHeaders, rlp.encode w) - - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceFetchBodies) = - buddy.toStream(TrtFetchBodies, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncBodies) = - buddy.toStream(TrtSyncBodies, rlp.encode w) - - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceImportBlock) = - buddy.toStream(TrtImportBlock, rlp.encode w) - -proc traceWrite*(buddy: BeaconBuddyRef; w: TraceSyncBlock) = - buddy.toStream(TrtSyncBlock, rlp.encode w) +proc writeValue*( + w: var JsonWriter; + v: chronos.Duration; + ) {.raises: [IOError].} = + ## Json writer mixin avoiding `{"value": NNN}` encapsulation + w.writeValue(cast[uint64](v.nanoseconds)) + +template traceWrite*(dsc: BeaconCtxRef|BeaconBuddyRef; capt: untyped) = + type T = typeof capt + const trp = T.toTraceRecType + when dsc is BeaconCtxRef: + dsc.toStream(trp, Json.encode(JTraceRecord[T](kind: trp, bag: capt))) + else: + dsc.ctx.toStream(trp, Json.encode(JTraceRecord[T](kind: trp, bag: capt))) # ------------------------------------------------------------------------------ # End From 9c8b527b6e4b032725a40742333972db7d924dce Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Fri, 12 Sep 2025 17:39:47 +0100 Subject: [PATCH 13/18] Simplify/prettify code details No need to prefix capture type names by `Trt`, so removed Simplify reader switches by using template+mixin Using static table for pretty printing capture record labels --- tools/syncer/replay/replay_desc.nim | 28 ++++++ .../replay/replay_reader/reader_helpers.nim | 53 +++++++++++- .../replay/replay_reader/reader_reclog.nim | 85 ++++++------------- .../replay/replay_reader/reader_unpack.nim | 53 +++--------- .../replay/replay_runner/runner_dispatch.nim | 72 ++++++++-------- .../runner_dispatch/dispatch_blocks.nim | 4 +- .../runner_dispatch/dispatch_headers.nim | 2 +- .../runner_dispatch/dispatch_sched.nim | 15 ++-- .../runner_dispatch/dispatch_sync.nim | 8 +- .../runner_dispatch/dispatch_version.nim | 3 +- tools/syncer/trace/trace_desc.nim | 70 +++++++-------- 11 files changed, 205 insertions(+), 188 deletions(-) diff --git a/tools/syncer/replay/replay_desc.nim b/tools/syncer/replay/replay_desc.nim index 79cab15632..b4bab66fb5 100644 --- a/tools/syncer/replay/replay_desc.nim +++ b/tools/syncer/replay/replay_desc.nim @@ -28,6 +28,30 @@ const ReplaySetupID* = 2 ## Phase 1 layout ID, prepare ReplayRunnerID* = 20 ## Phase 2 layout ID, full execution + ReplayTypeLabel* = block: + var a: array[TraceRecType,string] + a[TraceRecType(0)] = "=Oops" + a[VersionInfo] = "=Version" + a[SyncActvFailed] = "=ActvFailed" + a[SyncActivated] = "=Activated" + a[SyncHibernated] = "=Suspended" + a[SchedStart] = "=StartPeer" + a[SchedStop] = "=StopPeer" + a[SchedPool] = "=Pool" + a[SchedDaemonBegin] = "+Daemon" + a[SchedDaemonEnd] = "-Daemon" + a[SchedPeerBegin] = "+Peer" + a[SchedPeerEnd] = "-Peer" + a[FetchHeaders] = "=HeadersFetch" + a[SyncHeaders] = "=HeadersSync" + a[FetchBodies] = "=BodiesFetch" + a[SyncBodies] = "=BodiesSync" + a[ImportBlock] = "=BlockImport" + a[SyncBlock] = "=BlockSync" + for w in a: + doAssert 0 < w.len + a + type ReplayStopIfFn* = proc(): bool {.gcsafe, raises: [].} ## Loop control directive for runner/dispatcher @@ -118,6 +142,10 @@ func replay*(ctx: BeaconCtxRef): ReplayRef = if ctx.handler.version == ReplayRunnerID: return ctx.handler.ReplayRef +template replayLabel*(w: untyped): string = + ## Static getter, retrieve replay type label + ReplayTypeLabel[(typeof w).toTraceRecType] + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_helpers.nim b/tools/syncer/replay/replay_reader/reader_helpers.nim index 842b0127f4..66aede05d4 100644 --- a/tools/syncer/replay/replay_reader/reader_helpers.nim +++ b/tools/syncer/replay/replay_reader/reader_helpers.nim @@ -16,7 +16,8 @@ import std/strutils, pkg/[chronos, eth/common], ../../trace/trace_setup/setup_helpers as trace_helpers, - ../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers + ../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers, + ../replay_desc export trace_helpers.idStr, @@ -68,6 +69,56 @@ func toUpperFirst*(w: string): string = else: w +# ---------------- + +template withReplayTypeExpr*(recType: TraceRecType): untyped = + ## Big switch for allocating `TraceRecType` type dependent replay code + ## using the replay record layouts. + ## + mixin replayTypeExpr + + case recType: + of TraceRecType(0): + replayTypeExpr(TraceRecType(0), ReplayPayloadRef) + of VersionInfo: + replayTypeExpr(VersionInfo, ReplayVersionInfo) + + of SyncActvFailed: + replayTypeExpr(SyncActvFailed,ReplaySyncActvFailed) + of SyncActivated: + replayTypeExpr(SyncActivated, ReplaySyncActivated) + of SyncHibernated: + replayTypeExpr(SyncHibernated, ReplaySyncHibernated) + + of SchedDaemonBegin: + replayTypeExpr(SchedDaemonBegin, ReplaySchedDaemonBegin) + of SchedDaemonEnd: + replayTypeExpr(SchedDaemonEnd, ReplaySchedDaemonEnd) + of SchedStart: + replayTypeExpr(SchedStart, ReplaySchedStart) + of SchedStop: + replayTypeExpr(SchedStop, ReplaySchedStop) + of SchedPool: + replayTypeExpr(SchedPool, ReplaySchedPool) + of SchedPeerBegin: + replayTypeExpr(SchedPeerBegin, ReplaySchedPeerBegin) + of SchedPeerEnd: + replayTypeExpr(SchedPeerEnd, ReplaySchedPeerEnd) + + of FetchHeaders: + replayTypeExpr(FetchHeaders, ReplayFetchHeaders) + of SyncHeaders: + replayTypeExpr(SyncHeaders, ReplaySyncHeaders) + + of FetchBodies: + replayTypeExpr(FetchBodies, ReplayFetchBodies) + of SyncBodies: + replayTypeExpr(SyncBodies, ReplaySyncBodies) + of ImportBlock: + replayTypeExpr(ImportBlock, ReplayImportBlock) + of SyncBlock: + replayTypeExpr(SyncBlock, ReplaySyncBlock) + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim index 2562f7f029..e32eb91f63 100644 --- a/tools/syncer/replay/replay_reader/reader_reclog.nim +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -29,7 +29,7 @@ logScope: proc addX( q: var seq[string]; - info: static[string]; + info: string; lnr: int; base: TraceRecBase; ) = @@ -91,7 +91,7 @@ func toStrOops(n: int): seq[string] = func toStrSeq(n: int; w: TraceVersionInfo): seq[string] = var res = newSeqOfCap[string](15) - res.addX("=Version", n, w) + res.addX(w.replayLabel, n, w) let moan = if w.version < TraceVersionID: "(<" & $TraceVersionID & ")" elif TraceVersionID < w.version: "(>" & $TraceVersionID & ")" else: "" @@ -105,14 +105,14 @@ func toStrSeq(n: int; w: TraceVersionInfo): seq[string] = func toStrSeq(n: int; w: TraceSyncActvFailed): seq[string] = var res = newSeqOfCap[string](15) - res.addX("=ActvFailed", n, w) + res.addX(w.replayLabel, n, w) res.add "base=" & w.baseNum.bnStr res.add "latest=" & w.latestNum.bnStr res func toStrSeq(n: int; w: TraceSyncActivated): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=Activated", n, w) + res.addX(w.replayLabel, n, w) res.add "head=" & w.head.bnStr res.add "finHash=" & w.finHash.short res.add "base=" & w.baseNum.bnStr @@ -121,7 +121,7 @@ func toStrSeq(n: int; w: TraceSyncActivated): seq[string] = func toStrSeq(n: int; w: TraceSyncHibernated): seq[string] = var res = newSeqOfCap[string](15) - res.addX("=Suspended", n, w) + res.addX(w.replayLabel, n, w) res.add "base=" & w.baseNum.bnStr res.add "latest=" & w.latestNum.bnStr res @@ -130,17 +130,17 @@ func toStrSeq(n: int; w: TraceSyncHibernated): seq[string] = func toStrSeq(n: int; w: TraceSchedDaemonBegin): seq[string] = var res = newSeqOfCap[string](15) - res.addX("+Daemon", n, w) + res.addX(w.replayLabel, n, w) res func toStrSeq(n: int; w: TraceSchedDaemonEnd): seq[string] = var res = newSeqOfCap[string](15) - res.addX("-Daemon", n, w) + res.addX(w.replayLabel, n, w) res func toStrSeq(n: int; w: TraceSchedStart): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=StartPeer", n, w) + res.addX(w.replayLabel, n, w) res.add "peer=" & $w.peerIP & ":" & $w.peerPort if not w.accept: res.add "rejected" @@ -148,13 +148,13 @@ func toStrSeq(n: int; w: TraceSchedStart): seq[string] = func toStrSeq(n: int; w: TraceSchedStop): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=StopPeer", n, w) + res.addX(w.replayLabel, n, w) res.add "peer=" & $w.peerIP & ":" & $w.peerPort res func toStrSeq(n: int; w: TraceSchedPool): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=Pool", n, w) + res.addX(w.replayLabel, n, w) res.add "peer=" & $w.peerIP & ":" & $w.peerPort res.add "last=" & $w.last res.add "laps=" & $w.laps @@ -163,20 +163,20 @@ func toStrSeq(n: int; w: TraceSchedPool): seq[string] = func toStrSeq(n: int; w: TraceSchedPeerBegin): seq[string] = var res = newSeqOfCap[string](20) - res.addX("+Peer", n, w) + res.addX(w.replayLabel, n, w) res.add "peer=" & $w.peerIP & ":" & $w.peerPort res func toStrSeq(n: int; w: TraceSchedPeerEnd): seq[string] = var res = newSeqOfCap[string](15) - res.addX("-Peer", n, w) + res.addX(w.replayLabel, n, w) res # ----------- func toStrSeq(n: int; w: TraceFetchHeaders): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=HeadersFetch", n, w) + res.addX(w.replayLabel, n, w) let rLen = w.req.maxResults rRev = if w.req.reverse: "rev" else: "" @@ -201,13 +201,13 @@ func toStrSeq(n: int; w: TraceFetchHeaders): seq[string] = func toStrSeq(n: int; w: TraceSyncHeaders): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=HeadersSync", n, w) + res.addX(w.replayLabel, n, w) res func toStrSeq(n: int; w: TraceFetchBodies): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=BodiesFetch", n, w) + res.addX(w.replayLabel, n, w) res.add "req=" & w.ivReq.bnStr & "[" & $w.req.blockHashes.len & "]" if (w.fieldAvail and 1) != 0: res.add "res=[" & $w.fetched.packet.bodies.len & "]" @@ -225,13 +225,13 @@ func toStrSeq(n: int; w: TraceFetchBodies): seq[string] = func toStrSeq(n: int; w: TraceSyncBodies): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=BodiesSync", n, w) + res.addX(w.replayLabel, n, w) res func toStrSeq(n: int; w: TraceImportBlock): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=BlockImport", n, w) + res.addX(w.replayLabel, n, w) res.add "block=" & w.ethBlock.bnStr res.add "size=" & w.ethBlock.getEncodedLength.uint64.toSI res.add "effPeerID=" & w.effPeerID.short @@ -249,7 +249,7 @@ func toStrSeq(n: int; w: TraceImportBlock): seq[string] = func toStrSeq(n: int; w: TraceSyncBlock): seq[string] = var res = newSeqOfCap[string](20) - res.addX("=BlockSync", n, w) + res.addX(w.replayLabel, n, w) res # ------------------------------------------------------------------------------ @@ -306,48 +306,13 @@ proc recLogToStrList*(pyl: ReplayPayloadRef; lnr = 0): seq[string] = ## Convert the internal capture object argument `pyl` to a list of ## printable strings. ## - case pyl.recType: - of TraceRecType(0): - lnr.toStrOops() - - of TrtVersionInfo: - lnr.toStrSeq(pyl.ReplayVersionInfo.data) - - of TrtSyncActvFailed: - lnr.toStrSeq(pyl.ReplaySyncActvFailed.data) - of TrtSyncActivated: - lnr.toStrSeq(pyl.ReplaySyncActivated.data) - of TrtSyncHibernated: - lnr.toStrSeq(pyl.ReplaySyncHibernated.data) - - of TrtSchedDaemonBegin: - lnr.toStrSeq(pyl.ReplaySchedDaemonBegin.data) - of TrtSchedDaemonEnd: - lnr.toStrSeq(pyl.ReplaySchedDaemonEnd.data) - of TrtSchedStart: - lnr.toStrSeq(pyl.ReplaySchedStart.data) - of TrtSchedStop: - lnr.toStrSeq(pyl.ReplaySchedStop.data) - of TrtSchedPool: - lnr.toStrSeq(pyl.ReplaySchedPool.data) - of TrtSchedPeerBegin: - lnr.toStrSeq(pyl.ReplaySchedPeerBegin.data) - of TrtSchedPeerEnd: - lnr.toStrSeq(pyl.ReplaySchedPeerEnd.data) - - of TrtFetchHeaders: - lnr.toStrSeq(pyl.ReplayFetchHeaders.data) - of TrtSyncHeaders: - lnr.toStrSeq(pyl.ReplaySyncHeaders.data) - - of TrtFetchBodies: - lnr.toStrSeq(pyl.ReplayFetchBodies.data) - of TrtSyncBodies: - lnr.toStrSeq(pyl.ReplaySyncBodies.data) - of TrtImportBlock: - lnr.toStrSeq(pyl.ReplayImportBlock.data) - of TrtSyncBlock: - lnr.toStrSeq(pyl.ReplaySyncBlock.data) + template replayTypeExpr(t: TraceRecType, T: type): untyped = + when t == TraceRecType(0): + lnr.toStrOops() + else: + lnr.toStrSeq(pyl.T.data) + + pyl.recType.withReplayTypeExpr() # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/replay/replay_reader/reader_unpack.nim b/tools/syncer/replay/replay_reader/reader_unpack.nim index 37b5bff714..eee417b9e7 100644 --- a/tools/syncer/replay/replay_reader/reader_unpack.nim +++ b/tools/syncer/replay/replay_reader/reader_unpack.nim @@ -17,7 +17,8 @@ import pkg/[chronicles, chronos, eth/common, results, stew/interval_set], pkg/json_serialization/pkg/results, pkg/eth/common/eth_types_json_serialization, - ../replay_desc + ../replay_desc, + ./reader_helpers logScope: topics = "replay reader" @@ -253,47 +254,15 @@ proc unpack*(line: string): ReplayPayloadRef = ## const info = "unpack" - case line.getRecType(info): - of TraceRecType(0): - return ReplayPayloadRef(recType: TraceRecType(0)) - of TrtVersionInfo: - return ReplayVersionInfo.init(line, info) - - of TrtSyncActvFailed: - return ReplaySyncActvFailed.init(line, info) - of TrtSyncActivated: - return ReplaySyncActivated.init(line, info) - of TrtSyncHibernated: - return ReplaySyncHibernated.init(line, info) - - of TrtSchedDaemonBegin: - return ReplaySchedDaemonBegin.init(line, info) - of TrtSchedDaemonEnd: - return ReplaySchedDaemonEnd.init(line, info) - of TrtSchedStart: - return ReplaySchedStart.init(line, info) - of TrtSchedStop: - return ReplaySchedStop.init(line, info) - of TrtSchedPool: - return ReplaySchedPool.init(line, info) - of TrtSchedPeerBegin: - return ReplaySchedPeerBegin.init(line, info) - of TrtSchedPeerEnd: - return ReplaySchedPeerEnd.init(line, info) - - of TrtFetchHeaders: - return ReplayFetchHeaders.init(line, info) - of TrtSyncHeaders: - return ReplaySyncHeaders.init(line, info) - - of TrtFetchBodies: - return ReplayFetchBodies.init(line, info) - of TrtSyncBodies: - return ReplaySyncBodies.init(line, info) - of TrtImportBlock: - return ReplayImportBlock.init(line, info) - of TrtSyncBlock: - return ReplaySyncBlock.init(line, info) + template replayTypeExpr(t: TraceRecType, T: type): untyped = + ## Mixin for `withReplayTypeExpr()` + when t == TraceRecType(0): + return T(recType: TraceRecType(0)) + else: + return T.init(line, info) + + # Big switch for allocating different JSON parsers depending on record type. + line.getRecType(info).withReplayTypeExpr() # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/replay/replay_runner/runner_dispatch.nim b/tools/syncer/replay/replay_runner/runner_dispatch.nim index 9bc920302b..c7b2b4daad 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch.nim @@ -41,50 +41,50 @@ proc dispatch*( of TraceRecType(0): warn "dispatch(): Oops, unexpected void record", n=run.instrNumber - of TrtVersionInfo: - run.versionInfoWorker(pyl.ReplayVersionInfo.data, "=Version") + of VersionInfo: + run.versionInfoWorker(pyl.ReplayVersionInfo.data) - of TrtSyncActvFailed: - run.syncActvFailedWorker(pyl.ReplaySyncActvFailed.data, "=ActvFailed") - of TrtSyncActivated: - run.syncActivateWorker(pyl.ReplaySyncActivated.data, "=Activated") - of TrtSyncHibernated: - run.syncSuspendWorker(pyl.ReplaySyncHibernated.data, "=Suspended") + of SyncActvFailed: + run.syncActvFailedWorker(pyl.ReplaySyncActvFailed.data) + of SyncActivated: + run.syncActivateWorker(pyl.ReplaySyncActivated.data) + of SyncHibernated: + run.syncSuspendWorker(pyl.ReplaySyncHibernated.data) # Simple scheduler single run (no begin/end) functions - of TrtSchedStart: - run.schedStartWorker(pyl.ReplaySchedStart.data, "=StartPeer") - of TrtSchedStop: - run.schedStopWorker(pyl.ReplaySchedStop.data, "=StopPeer") - of TrtSchedPool: - run.schedPoolWorker(pyl.ReplaySchedPool.data, "=Pool") + of SchedStart: + run.schedStartWorker(pyl.ReplaySchedStart.data) + of SchedStop: + run.schedStopWorker(pyl.ReplaySchedStop.data) + of SchedPool: + run.schedPoolWorker(pyl.ReplaySchedPool.data) # Workers, complex run in background - of TrtSchedDaemonBegin: - await run.schedDaemonBegin(pyl.ReplaySchedDaemonBegin.data, "+Daemon") - of TrtSchedDaemonEnd: - await run.schedDaemonEnd(pyl.ReplaySchedDaemonEnd.data, "-Daemon") - of TrtSchedPeerBegin: - await run.schedPeerBegin(pyl.ReplaySchedPeerBegin.data, "+Peer") - of TrtSchedPeerEnd: - await run.schedPeerEnd(pyl.ReplaySchedPeerEnd.data, "-Peer") + of SchedDaemonBegin: + await run.schedDaemonBegin(pyl.ReplaySchedDaemonBegin.data) + of SchedDaemonEnd: + await run.schedDaemonEnd(pyl.ReplaySchedDaemonEnd.data) + of SchedPeerBegin: + await run.schedPeerBegin(pyl.ReplaySchedPeerBegin.data) + of SchedPeerEnd: + await run.schedPeerEnd(pyl.ReplaySchedPeerEnd.data) # Leaf handlers providing input data to background tasks `runDaemon()` # and/or `runPeer()`. - of TrtFetchHeaders: - await run.sendHeaders(pyl.ReplayFetchHeaders.data, "=HeadersFetch") - of TrtSyncHeaders: - await run.sendHeaders(pyl.ReplaySyncHeaders.data, "=HeadersSync") - - of TrtFetchBodies: - await run.sendBodies(pyl.ReplayFetchBodies.data, "=BodiesFetch") - of TrtSyncBodies: - await run.sendBodies(pyl.ReplaySyncBodies.data, "=BodiesSync") - - of TrtImportBlock: - await run.sendBlock(pyl.ReplayImportBlock.data, "=BlockImport") - of TrtSyncBlock: - await run.sendBlock(pyl.ReplaySyncBlock.data, "=BlockSync") + of FetchHeaders: + await run.sendHeaders(pyl.ReplayFetchHeaders.data) + of SyncHeaders: + await run.sendHeaders(pyl.ReplaySyncHeaders.data) + + of FetchBodies: + await run.sendBodies(pyl.ReplayFetchBodies.data) + of SyncBodies: + await run.sendBodies(pyl.ReplaySyncBodies.data) + + of ImportBlock: + await run.sendBlock(pyl.ReplayImportBlock.data) + of SyncBlock: + await run.sendBlock(pyl.ReplaySyncBlock.data) trace "-dispatch()", n=run.instrNumber, recType=pyl.recType, nBuddies=run.peers.len, nDaemons=(if run.daemon.isNil: 0 else: 1) diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim index 3c11c597bf..03cb2b3de0 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim @@ -190,9 +190,9 @@ proc importBlockHandler*( proc sendBodies*( run: ReplayRunnerRef; instr: TraceFetchBodies|TraceSyncBodies; - info: static[string]; ) {.async: (raises: []).} = ## Stage bodies request/response data + const info = instr.replayLabel() let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & @@ -203,9 +203,9 @@ proc sendBodies*( proc sendBlock*( run: ReplayRunnerRef; instr: TraceImportBlock|TraceSyncBlock; - info: static[string]; ) {.async: (raises: []).} = ## Stage block request/response data + const info = instr.replayLabel() if (instr.stateAvail and 2) != 0: # So it was captured run from a sync peer let buddy = run.getPeer(instr, info).valueOr: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim index e751de8789..363819afc5 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim @@ -96,9 +96,9 @@ proc fetchHeadersHandler*( proc sendHeaders*( run: ReplayRunnerRef; instr: TraceFetchHeaders|TraceSyncHeaders; - info: static[string]; ) {.async: (raises: []).} = ## Stage headers request/response data + const info = instr.replayLabel() let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim index 1d2ceb365d..f915a748a7 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim @@ -74,11 +74,11 @@ proc schedPeerProcessImpl( proc schedDaemonBegin*( run: ReplayRunnerRef; instr: TraceSchedDaemonBegin; - info: static[string]; ) {.async: (raises: []).} = ## Run the `schedDaemon()` task. ## # Synchronise against captured environment and start process + const info = instr.replayLabel() let daemon = run.newDaemonFrame(instr, info).valueOr: return discard await daemon.waitForSyncedEnv(instr, info) asyncSpawn daemon.schedDaemonProcessImpl(instr, info) @@ -87,10 +87,10 @@ proc schedDaemonBegin*( proc schedDaemonEnd*( run: ReplayRunnerRef; instr: TraceSchedDaemonEnd; - info: static[string]; ) {.async: (raises: []).} = ## Clean up (in foreground) after `schedDaemon()` process has terminated. ## + const info = instr.replayLabel() let daemon = run.getDaemon(info).valueOr: return daemon.whenProcessFinished(instr, info).isErrOr: daemon.delDaemon(info) # Clean up @@ -99,10 +99,11 @@ proc schedDaemonEnd*( proc schedStartWorker*( run: ReplayRunnerRef; instr: TraceSchedStart; - info: static[string]; ) = ## Runs `schedStart()` in the foreground. ## + const + info = instr.replayLabel() let buddy = run.newPeer(instr, info) accept = run.worker.schedStart(buddy) @@ -127,10 +128,10 @@ proc schedStartWorker*( proc schedStopWorker*( run: ReplayRunnerRef; instr: TraceSchedStop; - info: static[string]; ) = ## Runs `schedStop()` in the foreground. ## + const info = instr.replayLabel() let buddy = run.getOrNewPeerFrame(instr, info) run.worker.schedStop(buddy) @@ -154,10 +155,10 @@ proc schedStopWorker*( proc schedPoolWorker*( run: ReplayRunnerRef; instr: TraceSchedPool; - info: static[string]; ) = ## Runs `schedPool()` in the foreground. ## + const info = instr.replayLabel() let buddy = run.getOrNewPeerFrame(instr, info) if 0 < run.nPeers: @@ -183,11 +184,11 @@ proc schedPoolWorker*( proc schedPeerBegin*( run: ReplayRunnerRef; instr: TraceSchedPeerBegin; - info: static[string]; ) {.async: (raises: []).} = ## Run the `schedPeer()` task. ## # Synchronise against captured environment and start process + const info = instr.replayLabel() let buddy = run.getOrNewPeerFrame(instr, info) discard await buddy.waitForSyncedEnv(instr, info) asyncSpawn buddy.schedPeerProcessImpl(instr, info) @@ -196,10 +197,10 @@ proc schedPeerBegin*( proc schedPeerEnd*( run: ReplayRunnerRef; instr: TraceSchedPeerEnd; - info: static[string]; ) {.async: (raises: []).} = ## Clean up (in foreground) after `schedPeer()` process has terminated. ## + const info = instr.replayLabel() let buddy = run.getPeer(instr, info).valueOr: return buddy.whenProcessFinished(instr, info).isErrOr: buddy.run.nPeers.dec # peer is not active, anymore diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim index 36b5218312..67956eb0fc 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim @@ -28,15 +28,17 @@ logScope: proc syncActvFailedWorker*( run: ReplayRunnerRef; instr: TraceSyncActvFailed; - info: static[string]; ) = + const info = instr.replayLabel() trace info, n=run.iNum, serial=instr.serial proc syncActivateWorker*( run: ReplayRunnerRef; instr: TraceSyncActivated; - info: static[string]) = + ) = + const + info = instr.replayLabel() let serial = instr.serial ctx = run.ctx @@ -71,8 +73,8 @@ proc syncActivateWorker*( proc syncSuspendWorker*( run: ReplayRunnerRef; instr: TraceSyncHibernated; - info: static[string]; ) = + const info = instr.replayLabel() if not run.ctx.hibernate: run.stopError(info & ": suspend failed") return diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim index ba680257b4..6baafb0d99 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim @@ -28,8 +28,9 @@ logScope: proc versionInfoWorker*( run: ReplayRunnerRef; instr: TraceVersionInfo; - info: static[string]; ) = + const + info = instr.replayLabel() let serial = instr.serial ctx = run.ctx diff --git a/tools/syncer/trace/trace_desc.nim b/tools/syncer/trace/trace_desc.nim index f757d4ce07..91de105df2 100644 --- a/tools/syncer/trace/trace_desc.nim +++ b/tools/syncer/trace/trace_desc.nim @@ -51,29 +51,29 @@ type # ------------- TraceRecType* = enum - TrtRecBase = 0 - TrtVersionInfo = 1 + RecBase = 0 + VersionInfo = 1 - TrtSyncActvFailed - TrtSyncActivated - TrtSyncHibernated + SyncActvFailed + SyncActivated + SyncHibernated - TrtSchedDaemonBegin - TrtSchedDaemonEnd - TrtSchedStart - TrtSchedStop - TrtSchedPool - TrtSchedPeerBegin - TrtSchedPeerEnd + SchedDaemonBegin + SchedDaemonEnd + SchedStart + SchedStop + SchedPool + SchedPeerBegin + SchedPeerEnd - TrtFetchHeaders - TrtSyncHeaders + FetchHeaders + SyncHeaders - TrtFetchBodies - TrtSyncBodies + FetchBodies + SyncBodies - TrtImportBlock - TrtSyncBlock + ImportBlock + SyncBlock TraceRecBase* = object of RootObj ## Trace context applicable with and without known peer @@ -219,39 +219,39 @@ func newSerial*(trc: TraceRef): uint64 = func toTraceRecType*(T: type): TraceRecType = ## Derive capture type from record layout when T is TraceVersionInfo: - TrtVersionInfo + VersionInfo elif T is TraceSyncActvFailed: - TrtSyncActvFailed + SyncActvFailed elif T is TraceSyncActivated: - TrtSyncActivated + SyncActivated elif T is TraceSyncHibernated: - TrtSyncHibernated + SyncHibernated elif T is TraceSchedDaemonBegin: - TrtSchedDaemonBegin + SchedDaemonBegin elif T is TraceSchedDaemonEnd: - TrtSchedDaemonEnd + SchedDaemonEnd elif T is TraceSchedStart: - TrtSchedStart + SchedStart elif T is TraceSchedStop: - TrtSchedStop + SchedStop elif T is TraceSchedPool: - TrtSchedPool + SchedPool elif T is TraceSchedPeerBegin: - TrtSchedPeerBegin + SchedPeerBegin elif T is TraceSchedPeerEnd: - TrtSchedPeerEnd + SchedPeerEnd elif T is TraceFetchHeaders: - TrtFetchHeaders + FetchHeaders elif T is TraceSyncHeaders: - TrtSyncHeaders + SyncHeaders elif T is TraceFetchBodies: - TrtFetchBodies + FetchBodies elif T is TraceSyncBodies: - TrtSyncBodies + SyncBodies elif T is TraceImportBlock: - TrtImportBlock + ImportBlock elif T is TraceSyncBlock: - TrtSyncBlock + SyncBlock else: {.error: "Unsupported trace capture record type".} From 0d154f00be7632cf0949b750ba4b0f3eff4941b6 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Tue, 16 Sep 2025 12:43:15 +0100 Subject: [PATCH 14/18] Replace bit-mask controlled optional capture fields by `Opt[]` structures why Contrary to RLP which was not fully reliable at the time of defining the capture record layout, JSON is fully capable of handling `Opt[]` constructs. --- .../replay/replay_reader/reader_reclog.nim | 99 +++---- .../replay/replay_runner/runner_desc.nim | 4 +- .../runner_dispatch/dispatch_blocks.nim | 37 +-- .../runner_dispatch/dispatch_headers.nim | 12 +- .../runner_dispatch/dispatch_helpers.nim | 256 +++++++++++------- .../runner_dispatch/dispatch_sched.nim | 37 +-- tools/syncer/trace/trace_desc.nim | 70 ++--- .../syncer/trace/trace_setup/setup_blocks.nim | 16 +- .../trace/trace_setup/setup_headers.nim | 6 +- .../trace/trace_setup/setup_helpers.nim | 43 +-- .../syncer/trace/trace_setup/setup_sched.nim | 33 +-- 11 files changed, 338 insertions(+), 275 deletions(-) diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim index e32eb91f63..5bcc652027 100644 --- a/tools/syncer/replay/replay_reader/reader_reclog.nim +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -41,8 +41,8 @@ proc addX( else: q.add $base.serial - if 0 < base.frameID: - q.add base.frameID.idStr + if base.frameID.isSome(): + q.add base.frameID.value.idStr else: q.add "*" q.add $base.nPeers @@ -57,28 +57,33 @@ proc addX( else: q.add "*" - q.add (if (base.stateAvail and 1) != 0: $base.peerCtrl else: "*") - q.add (if (base.stateAvail and 2) != 0: "peerID=" & base.peerID.short() - else: "*") + if base.peerCtx.isSome(): + q.add $base.peerCtx.value.peerCtrl + q.add "peerID=" & base.peerCtx.value.peerID.short() + else: + q.add "*" + q.add "*" - if 0 < base.hdrUnprChunks: - q.add "uHdr=" & $base.hdrUnprLen & "/" & - $base.hdrUnprChunks & "/" & - $base.hdrUnprLastLen & ":" & - $base.hdrUnprLast.bnStr + if base.hdrUnpr.isSome(): + q.add "uHdr=" & $base.hdrUnpr.value.hLen & "/" & + $base.hdrUnpr.value.hChunks & "/" & + $base.hdrUnpr.value.hLastLen & ":" & + $base.hdrUnpr.value.hLast.bnStr - if 0 < base.blkUnprChunks: - q.add "uBlk=" & $base.blkUnprLen & "/" & - $base.blkUnprChunks & "/" & - $base.blkUnprLeast.bnStr & ":" & - $base.blkUnprLeastLen + if base.blkUnpr.isSome(): + q.add "uBlk=" & $base.blkUnpr.value.bLen & "/" & + $base.blkUnpr.value.bChunks & "/" & + $base.blkUnpr.value.bLeast.bnStr & ":" & + $base.blkUnpr.value.bLeastLen - if (base.stateAvail and 12) != 0 and - (0 < base.nHdrErrors or 0 < base.nBlkErrors): - q.add "nErr=(" & $base.nHdrErrors & "," & $base.nBlkErrors & ")" + if base.peerCtx.isSome() and + (0 < base.peerCtx.value.nHdrErrors or + 0 < base.peerCtx.value.nBlkErrors): + q.add "nErr=(" & $base.peerCtx.value.nHdrErrors & + "," & $base.peerCtx.value.nBlkErrors & ")" - if (base.stateAvail and 16) != 0: - q.add "slowPeer=" & base.slowPeer.short() + if base.slowPeer.isSome(): + q.add "slowPeer=" & base.slowPeer.value.short() # ------------------------------------------------------------------------------ # Private record handlers @@ -186,17 +191,17 @@ func toStrSeq(n: int; w: TraceFetchHeaders): seq[string] = res.add "req=" & w.req.startBlock.number.bnStr & "[" & $rLen & "]" & rRev if 0 < w.req.skip: res.add "skip=" & $w.req.skip - if (w.fieldAvail and 1) != 0: - res.add "res=[" & $w.fetched.packet.headers.len & "]" - res.add "ela=" & w.fetched.elapsed.toStr - if (w.fieldAvail and 2) != 0: - if w.error.excp.ord == 0: + if w.fetched.isSome(): + res.add "res=[" & $w.fetched.value.packet.headers.len & "]" + res.add "ela=" & w.fetched.value.elapsed.toStr + if w.error.isSome(): + if w.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.excp).substr(1) - if w.error.msg.len != 0: - res.add "error=" & w.error.name & "(" & w.error.msg & ")" - res.add "ela=" & w.error.elapsed.toStr + res.add "excp=" & ($w.error.value.excp).substr(1) + if w.error.value.msg.len != 0: + res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" + res.add "ela=" & w.error.value.elapsed.toStr res func toStrSeq(n: int; w: TraceSyncHeaders): seq[string] = @@ -209,18 +214,18 @@ func toStrSeq(n: int; w: TraceFetchBodies): seq[string] = var res = newSeqOfCap[string](20) res.addX(w.replayLabel, n, w) res.add "req=" & w.ivReq.bnStr & "[" & $w.req.blockHashes.len & "]" - if (w.fieldAvail and 1) != 0: - res.add "res=[" & $w.fetched.packet.bodies.len & "]" - res.add "size=" & w.fetched.packet.bodies.getEncodedLength.uint64.toSI - res.add "ela=" & w.fetched.elapsed.toStr - if (w.fieldAvail and 2) != 0: - if w.error.excp.ord == 0: + if w.fetched.isSome(): + res.add "res=[" & $w.fetched.value.packet.bodies.len & "]" + res.add "size=" & w.fetched.value.packet.bodies.getEncodedLength.uint64.toSI + res.add "ela=" & w.fetched.value.elapsed.toStr + if w.error.isSome(): + if w.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.excp).substr(1) - if w.error.msg.len != 0: - res.add "error=" & w.error.name & "(" & w.error.msg & ")" - res.add "ela=" & w.error.elapsed.toStr + res.add "excp=" & ($w.error.value.excp).substr(1) + if w.error.value.msg.len != 0: + res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" + res.add "ela=" & w.error.value.elapsed.toStr res func toStrSeq(n: int; w: TraceSyncBodies): seq[string] = @@ -235,16 +240,16 @@ func toStrSeq(n: int; w: TraceImportBlock): seq[string] = res.add "block=" & w.ethBlock.bnStr res.add "size=" & w.ethBlock.getEncodedLength.uint64.toSI res.add "effPeerID=" & w.effPeerID.short - if (w.fieldAvail and 1) != 0: - res.add "ela=" & w.elapsed.toStr - if (w.fieldAvail and 2) != 0: - if w.error.excp.ord == 0: + if w.elapsed.isSome(): + res.add "ela=" & w.elapsed.value.toStr + if w.error.isSome(): + if w.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.excp).substr(1) - if w.error.msg.len != 0: - res.add "error=" & w.error.name & "(" & w.error.msg & ")" - res.add "ela=" & w.error.elapsed.toStr + res.add "excp=" & ($w.error.value.excp).substr(1) + if w.error.value.msg.len != 0: + res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" + res.add "ela=" & w.error.value.elapsed.toStr res func toStrSeq(n: int; w: TraceSyncBlock): seq[string] = diff --git a/tools/syncer/replay/replay_runner/runner_desc.nim b/tools/syncer/replay/replay_runner/runner_desc.nim index e23aa3f459..c4c86e6557 100644 --- a/tools/syncer/replay/replay_runner/runner_desc.nim +++ b/tools/syncer/replay/replay_runner/runner_desc.nim @@ -79,13 +79,13 @@ type ## Replacement of `BeaconBuddyRef` in `runPeer()` and `runPool()` isNew*: bool ## Set in `getOrNewPeer()` when created run*: ReplayRunnerRef ## Back-reference for convenience - frameID*: uint64 ## Begin/end frame + frameID*: Opt[uint] ## Begin/end frame message*: ReplayMsgRef ## Data message channel ReplayDaemonRef* = ref object ## Daemeon job frame (similar to `ReplayBuddyRef`) run*: ReplayRunnerRef ## Back-reference for convenience - frameID*: uint64 ## Begin/end frame + frameID*: Opt[uint] ## Begin/end frame message*: ReplayMsgRef ## Data message channel # --------- diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim index 03cb2b3de0..2fb8cafba6 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim @@ -60,18 +60,22 @@ proc toStr(e: BeaconError; anyTime = false): string = func getResponse( instr: TraceFetchBodies; ): Result[FetchBodiesData,BeaconError] = - if (instr.fieldAvail and 1) != 0: - ok(instr.fetched) + if instr.fetched.isSome(): + ok(instr.fetched.value) + elif instr.error.isSome(): + err(instr.error.value) else: - err(instr.error) + err((ENoException,"","Missing fetch bodies return code",Duration())) func getResponse( instr: TraceImportBlock; ): Result[Duration,BeaconError] = - if (instr.fieldAvail and 1) != 0: - ok(instr.elapsed) + if instr.elapsed.isSome(): + ok(instr.elapsed.value) + elif instr.error.isSome(): + err(instr.error.value) else: - err(instr.error) + err((ENoException,"","Missing block import return code",Duration())) func getBeaconError(e: ReplayWaitError): BeaconError = (e[0], e[1], e[2], Duration()) @@ -162,19 +166,20 @@ proc importBlockHandler*( rpl = ctx.replay if not rpl.runner.fakeImport: let rc = await rpl.backup.importBlock(buddy, ethBlock, effPeerID) - if rc.isErr or (data.fieldAvail and 2) != 0: + if rc.isErr or data.error.isSome(): const info = info & ": result values differ" let serial = data.serial - if rc.isErr and (data.fieldAvail and 2) == 0: + if rc.isErr and data.error.isNone(): warn info, n, serial, peer, peerID, got="err" & rc.error.toStr, expected="ok" - elif rc.isOk and (data.fieldAvail and 2) != 0: + elif rc.isOk and data.error.isSome(): warn info, n, serial, peer, peerID, - got="ok", expected="err" & data.error.toStr(true) - elif rc.error.excp != data.error.excp or - rc.error.msg != data.error.msg: + got="ok", expected="err" & data.error.value.toStr(true) + elif rc.error.excp != data.error.value.excp or + rc.error.msg != data.error.value.msg: warn info, n, serial, peer, peerID, - got="err" & rc.error.toStr, expected="err" & data.error.toStr(true) + got="err" & rc.error.toStr, + expected="err" & data.error.value.toStr(true) buddy.withInstr(TraceSyncBlock, info): if not instr.isAvailable(): @@ -197,7 +202,7 @@ proc sendBodies*( raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & ", serial=" & $instr.serial & - ", peerID=" & instr.peerID.short + ", peerID=" & instr.peerCtx.value.peerID.short discard buddy.pushInstr(instr, info) proc sendBlock*( @@ -206,13 +211,13 @@ proc sendBlock*( ) {.async: (raises: []).} = ## Stage block request/response data const info = instr.replayLabel() - if (instr.stateAvail and 2) != 0: + if instr.peerCtx.isSome(): # So it was captured run from a sync peer let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & ", serial=" & $instr.serial & - ", peerID=" & instr.peerID.short + ", peerID=" & instr.peerCtx.value.peerID.short discard buddy.pushInstr(instr, info) # Verify that the daemon is properly initialised diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim index 363819afc5..286741b04b 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim @@ -40,10 +40,12 @@ proc `==`(a,b: BlockHeadersRequest): bool = func getResponse( instr: TraceFetchHeaders; ): Result[FetchHeadersData,BeaconError] = - if (instr.fieldAvail and 1) != 0: - ok(instr.fetched) + if instr.fetched.isSome(): + ok(instr.fetched.value) + elif instr.error.isSome(): + err(instr.error.value) else: - err(instr.error) + err((ENoException,"","Missing fetch headers return code",Duration())) func getBeaconError(e: ReplayWaitError): BeaconError = (e[0], e[1], e[2], Duration()) @@ -69,7 +71,6 @@ proc fetchHeadersHandler*( raiseAssert info & ": arguments differ" & ", n=" & $buddy.iNum & ", serial=" & $instr.serial & - ", frameID=" & instr.frameID.idStr & ", peer=" & $buddy.peer & # ----- ", reverse=" & $req.reverse & @@ -102,8 +103,7 @@ proc sendHeaders*( let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & - ", serial=" & $instr.serial & - ", peerID=" & instr.peerID.short + ", serial=" & $instr.serial discard buddy.pushInstr(instr, info) # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim index daedad385e..05d8866d5f 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim @@ -116,27 +116,29 @@ func syncedEnvCondImpl( ## let ctx = desc.run.ctx - if instr.hdrUnprChunks != ctx.hdr.unprocessed.chunks().uint: - return false - if 0 < instr.hdrUnprChunks: - if instr.hdrUnprLen != ctx.hdr.unprocessed.total(): + if instr.hdrUnpr.isSome(): + if instr.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: return false - let iv = ctx.hdr.unprocessed.le().expect "valid iv" - if instr.hdrUnprLast != iv.maxPt or - instr.hdrUnprLastLen != iv.len: - return false - if instr.antecedent != ctx.hdrCache.antecedent.number: - return false - - if instr.blkUnprChunks != ctx.blk.unprocessed.chunks().uint: - return false - if 0 < instr.blkUnprChunks: - if instr.blkUnprLen != ctx.blk.unprocessed.total(): - return false - let iv = ctx.blk.unprocessed.ge().expect "valid iv" - if instr.blkUnprLeast != iv.minPt or - instr.blkUnprLeastLen != iv.len: + if 0 < instr.hdrUnpr.value.hChunks: + if instr.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): + return false + let iv = ctx.hdr.unprocessed.le().expect "valid iv" + if instr.hdrUnpr.value.hLast != iv.maxPt or + instr.hdrUnpr.value.hLastLen != iv.len: + return false + if instr.antecedent != ctx.hdrCache.antecedent.number: + return false + + if instr.blkUnpr.isSome(): + if instr.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: return false + if 0 < instr.blkUnpr.value.bChunks: + if instr.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): + return false + let iv = ctx.blk.unprocessed.ge().expect "valid iv" + if instr.blkUnpr.value.bLeast != iv.minPt or + instr.blkUnpr.value.bLeastLen != iv.len: + return false return true @@ -145,23 +147,27 @@ proc newPeerImpl( run: ReplayRunnerRef; instr: TraceSchedStart|TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; info: static[string]; - ): ReplayBuddyRef = + ): Opt[ReplayBuddyRef] = ## Register a new peer. ## - run.peers.withValue(instr.peerID, val): + if instr.peerCtx.isNone(): + warn info & ": missing peer ctx", n=run.instrNumber, serial=instr.serial + return err() + + run.peers.withValue(instr.peerCtx.value.peerID, val): warn info & ": peer exists already", n=run.instrNumber, serial=instr.serial, peer=($val.peer) val.isNew = false - return val[] + return ok(val[]) var buddy = ReplayBuddyRef( isNew: true, run: run, ctx: run.ctx, only: BeaconBuddyData( - nRespErrors: (instr.nHdrErrors, - instr.nBlkErrors)), - peerID: instr.peerID, + nRespErrors: (instr.peerCtx.value.nHdrErrors, + instr.peerCtx.value.nBlkErrors)), + peerID: instr.peerCtx.value.peerID, peer: Peer( dispatcher: run.ethState.capa, peerStates: run.ethState.prots, @@ -172,8 +178,8 @@ proc newPeerImpl( tcpPort: instr.peerPort, udpPort: instr.peerPort))))) - run.peers[instr.peerID] = buddy - return (move buddy) + run.peers[instr.peerCtx.value.peerID] = buddy + return ok(move buddy) # ------------------------------------------------------------------------------ # Private functions, environment checkers @@ -262,74 +268,86 @@ proc unprocListsDifferImpl( statesDiffer = false # Unprocessed block numbers for header - if instr.hdrUnprChunks != ctx.hdr.unprocessed.chunks().uint: - statesDiffer = true - info info & ": unproc headers lists differ", n, serial, peer, - listChunks=ctx.hdr.unprocessed.chunks(), expected=instr.hdrUnprChunks - if 0 < instr.hdrUnprChunks: - if instr.hdrUnprLen != ctx.hdr.unprocessed.total(): - statesDiffer = true - info info & ": unproc headers lists differ", n, serial, peer, - listLen=ctx.hdr.unprocessed.total(), expected=instr.hdrUnprLen - let iv = ctx.hdr.unprocessed.le().expect "valid iv" - if instr.hdrUnprLastLen != iv.len: + if instr.hdrUnpr.isSome(): + if instr.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: statesDiffer = true info info & ": unproc headers lists differ", n, serial, peer, - lastIvLen=iv.len, expected=instr.hdrUnprLastLen - if instr.hdrUnprLast != iv.maxPt: - statesDiffer = true - info info & ": unproc headers lists differ", n, serial, peer, - lastIvMax=iv.maxPt, expected=instr.hdrUnprLast + listChunks=ctx.hdr.unprocessed.chunks(), + expected=instr.hdrUnpr.value.hChunks + if 0 < instr.hdrUnpr.value.hChunks: + if instr.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + listLen=ctx.hdr.unprocessed.total(), + expected=instr.hdrUnpr.value.hLen + let iv = ctx.hdr.unprocessed.le().expect "valid iv" + if instr.hdrUnpr.value.hLastLen != iv.len: + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + lastIvLen=iv.len, expected=instr.hdrUnpr.value.hLastLen + if instr.hdrUnpr.value.hLast != iv.maxPt: + statesDiffer = true + info info & ": unproc headers lists differ", n, serial, peer, + lastIvMax=iv.maxPt, expected=instr.hdrUnpr.value.hLast # Unprocessed block numbers for blocks - if instr.blkUnprChunks != ctx.blk.unprocessed.chunks().uint: - statesDiffer = true - info info & ": unproc blocks lists differ", n, serial, peer, - listChunks=ctx.blk.unprocessed.chunks(), expected=instr.blkUnprChunks - if 0 < instr.blkUnprChunks: - if instr.blkUnprLen != ctx.blk.unprocessed.total(): - statesDiffer = true - info info & ": unproc blocks lists differ", n, serial, peer, - listLen=ctx.blk.unprocessed.total(), expected=instr.blkUnprLen - let iv = ctx.blk.unprocessed.ge().expect "valid iv" - if instr.blkUnprLeastLen != iv.len: + if instr.blkUnpr.isSome(): + if instr.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: statesDiffer = true info info & ": unproc blocks lists differ", n, serial, peer, - lastIvLen=iv.len, expected=instr.blkUnprLeastLen - if instr.blkUnprLeast != iv.minPt: - statesDiffer = true - info info & ": unproc blocks lists differ", n, serial, peer, - lastIvMax=iv.maxPt, expected=instr.blkUnprLeast + listChunks=ctx.blk.unprocessed.chunks(), + expected=instr.blkUnpr.value.bChunks + if 0 < instr.blkUnpr.value.bChunks: + if instr.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + listLen=ctx.blk.unprocessed.total(), + expected=instr.blkUnpr.value.bLen + let iv = ctx.blk.unprocessed.ge().expect "valid iv" + if instr.blkUnpr.value.bLeastLen != iv.len: + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + lastIvLen=iv.len, expected=instr.blkUnpr.value.bLeastLen + if instr.blkUnpr.value.bLeast != iv.minPt: + statesDiffer = true + info info & ": unproc blocks lists differ", n, serial, peer, + lastIvMax=iv.maxPt, expected=instr.blkUnpr.value.bLeast return statesDiffer proc peerStatesDifferImpl( - desc: ReplayBuddyRef; + buddy: ReplayBuddyRef; instr: TraceRecBase; info: static[string]; ): bool = let - peer = desc.peer - n = desc.run.instrNumber + peer = buddy.peer + n = buddy.run.instrNumber serial = instr.serial var statesDiffer = false - if desc.ctrl.state != instr.peerCtrl: + if instr.peerCtx.isNone(): statesDiffer = true - info info & ": peer ctrl states differ", n, serial, peer, - ctrl=desc.ctrl.state, expected=instr.peerCtrl + info info & ": peer ctx values differ", n, serial, peer, ctx="n/a" + else: + if instr.peerCtx.value.peerCtrl != buddy.ctrl.state: + statesDiffer = true + info info & ": peer ctrl states differ", n, serial, peer, + ctrl=buddy.ctrl.state, expected=instr.peerCtx.value.peerCtrl - if instr.nHdrErrors != desc.only.nRespErrors.hdr: - statesDiffer = true - info info & ": peer header errors differ", n, serial, peer, - nHdrErrors=desc.only.nRespErrors.hdr, expected=instr.nHdrErrors + if instr.peerCtx.value.nHdrErrors != buddy.only.nRespErrors.hdr: + statesDiffer = true + info info & ": peer header errors differ", n, serial, peer, + nHdrErrors=buddy.only.nRespErrors.hdr, + expected=instr.peerCtx.value.nHdrErrors - if instr.nBlkErrors != desc.only.nRespErrors.blk: - statesDiffer = true - info info & ": peer body errors differ", n, serial, peer, - nBlkErrors=desc.only.nRespErrors.blk, expected=instr.nBlkErrors + if instr.peerCtx.value.nBlkErrors != buddy.only.nRespErrors.blk: + statesDiffer = true + info info & ": peer body errors differ", n, serial, peer, + nBlkErrors=buddy.only.nRespErrors.blk, + expected=instr.peerCtx.value.nBlkErrors return statesDiffer @@ -360,6 +378,18 @@ func peerIdStr*(desc: ReplayInstance): string = elif desc is ReplayDaemonRef: "n/a" +func frameIdStr*(instr: InstrType|TraceSchedStop|TraceSchedPool): string = + if instr.frameID.isSome(): + instr.frameID.value.idStr + else: + "n/a" + +func frameIdStr*(desc: ReplayBuddyRef|ReplayDaemonRef): string = + if desc.frameID.isSome(): + desc.frameID.value.idStr + else: + "n/a" + # ----------------- proc stopError*(run: ReplayRunnerRef; info: static[string]) = @@ -413,11 +443,13 @@ proc getPeer*( info: static[string]; ): Opt[ReplayBuddyRef] = ## Get peer from peers table (if any) - run.peers.withValue(instr.peerID, buddy): - return ok(buddy[]) - - debug info & ": no peer", n=run.iNum, serial=instr.serial, - peerID=instr.peerID.short + if instr.peerCtx.isNone(): + warn info & ": missing peer ctx", n=run.iNum, serial=instr.serial + else: + run.peers.withValue(instr.peerCtx.value.peerID, buddy): + return ok(buddy[]) + debug info & ": no peer", n=run.iNum, serial=instr.serial, + peerID=instr.peerCtx.value.peerID.short return err() @@ -425,7 +457,7 @@ proc newPeer*( run: ReplayRunnerRef; instr: TraceSchedStart; info: static[string]; - ): ReplayBuddyRef = + ): Opt[ReplayBuddyRef] = ## Register a new peer. ## return run.newPeerImpl(instr, info) @@ -435,22 +467,28 @@ proc getOrNewPeerFrame*( run: ReplayRunnerRef; instr: TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; info: static[string]; - ): ReplayBuddyRef = + ): Opt[ReplayBuddyRef] = ## Get an existing one or register a new peer and set up `stage[0]`. ## + if instr.peerCtx.isNone(): + return err() + var buddy: ReplayBuddyRef - run.peers.withValue(instr.peerID, val): + run.peers.withValue(instr.peerCtx.value.peerID, val): buddy = val[] buddy.isNew = false do: - buddy = run.newPeerImpl(instr, info) + buddy = run.newPeerImpl(instr, info).expect "valid peer" - if buddy.frameID == 0: - buddy.frameID = instr.frameID - elif buddy.frameID != instr.frameID: - error info & ": frame unexpected", n=buddy.iNum, serial=instr.serial, - frameID=buddy.frameID.idStr, expected=instr.frameID.idStr - return move(buddy) + if buddy.frameID.isSome(): + warn info & ": peer frameID unexpected", n=buddy.iNum, + serial=instr.serial, frameID=buddy.frameIdStr, expected="n/a" + if instr.frameID.isNone(): + warn info & ": peer instr frameID missing", n=buddy.iNum, + serial=instr.serial, frameID="n/a" + + buddy.frameID = instr.frameID + return ok(move buddy) proc delPeer*( @@ -488,13 +526,16 @@ proc newDaemonFrame*( ): Opt[ReplayDaemonRef] = ## Similar to `getOrNewPeerFrame()` for daemon. if run.daemon.isNil: + if instr.frameID.isNone(): + warn info & ": daemon instr frameID missing", n=run.iNum, + serial=instr.serial, frameID="n/a" run.daemon = ReplayDaemonRef( run: run, frameID: instr.frameID) return ok(run.daemon) warn info & ": daemon already registered", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr + frameID=instr.frameIdStr return err() @@ -525,7 +566,10 @@ proc waitForSyncedEnv*( # The scheduler (see `sync_sched.nim`) might have disconnected the peer # already as is captured in the instruction environment. This does not # apply to `zombie` settings which will be done by the application. - if instr.peerCtrl == Stopped and not desc.ctrl.stopped: + if instr.peerCtx.isNone(): + warn info & ": missing peer ctx", n=desc.iNum, serial=instr.serial + return err((ENoException,"",info&": missing peer ctx")) + if instr.peerCtx.value.peerCtrl == Stopped and not desc.ctrl.stopped: desc.ctrl.stopped = true let @@ -554,21 +598,23 @@ proc waitForSyncedEnv*( # ------------------ -proc processFinished*( +proc processFinishedClearFrame*( desc: ReplayInstance; - instr: TraceSchedDaemonBegin|TraceSchedPeerBegin; + instr: TraceSchedDaemonBegin|TraceSchedPeerBegin|TraceSchedPool; info: static[string]; ) = ## Register that the process has finished ## # Verify that sub-processes did not change the environment - doAssert desc.frameID == instr.frameID + if desc.frameID != instr.frameID: + warn info & ": frameIDs differ", n=desc.iNum, serial=instr.serial, + peer=desc.peerStr, frameID=desc.frameIdStr, expected=instr.frameIdStr # Mark the pocess `done` - desc.frameID = 0 + desc.frameID = Opt.none(uint) trace info & ": terminating", n=desc.iNum, - serial=instr.serial, frameID=instr.frameID.idStr, peer=desc.peerStr + serial=instr.serial, frameID=instr.frameIdStr, peer=desc.peerStr template whenProcessFinished*( @@ -590,22 +636,22 @@ template whenProcessFinished*( peerID {.inject,used.} = desc.peerIdStr serial {.inject,used.} = instr.serial - if desc.frameID != 0: + if desc.frameID.isSome(): doAssert desc.frameID == instr.frameID trace info & ": wait for terminated", n=desc.iNum, serial, - frameID=instr.frameID.idStr, peer, peerID + frameID=instr.frameIdStr, peer, peerID bodyRc = desc.run.waitForConditionImpl(info): if tmoPending: debug info & ": wait for terminated", n, serial, peer, count - desc.frameID == 0 + desc.frameID.isNone() if bodyRc.isErr: break body trace info & ": terminated OK", n=desc.iNum, serial, - frameID=instr.frameID.idStr, peer + frameID=instr.frameIdStr, peer desc.checkSyncerState(instr, ignLatestNum=true, info) # relaxed check # Synchronise against captured environment @@ -619,7 +665,7 @@ template whenProcessFinished*( break body trace info & ": finished", n=desc.iNum, serial, - frameID=instr.frameID.idStr, peer, peerID + frameID=instr.frameIdStr, peer, peerID # End body bodyRc # result @@ -642,7 +688,7 @@ template pushInstr*( type M = (typeof instr).toReplayMsgType # Verify that the stage is based on a proper environment - doAssert desc.frameID != 0 # this is not `instr.frameID` + doAssert desc.frameID.isSome() # this is not `instr.frameID` let peer {.inject,used.} = desc.peerStr @@ -741,7 +787,11 @@ template withInstr*( # peer already which would be captured in the instruction environment. # This does not apply to `zombie` settings which will be handled by # the application `code`. - if instr.peerCtrl == Stopped and not desc.ctrl.stopped: + if instr.peerCtx.isNone(): + warn info & ": missing peer ctx", n=desc.iNum, serial=instr.serial, + frameID=instr.frameIdStr, peer, peerID, dataType + desc.ctrl.stopped = true + elif instr.peerCtx.value.peerCtrl == Stopped and not desc.ctrl.stopped: desc.ctrl.stopped = true else: iError = rc.error @@ -758,7 +808,7 @@ template withInstr*( desc.checkSyncerState(instr, ignLatestNum, info) debug info & ": got data", n=desc.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer, peerID, dataType + frameID=instr.frameIdStr, peer, peerID, dataType desc.message = M(nil) diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim index f915a748a7..aa4e661f08 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim @@ -35,13 +35,13 @@ proc schedDaemonProcessImpl( ## let run = daemon.run trace info & ": begin", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, syncState=instr.syncState + frameID=instr.frameIdStr, syncState=instr.syncState discard await run.worker.schedDaemon(run.ctx) - daemon.processFinished(instr, info) + daemon.processFinishedClearFrame(instr, info) trace info & ": end", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, syncState=instr.syncState + frameID=instr.frameIdStr, syncState=instr.syncState proc schedPeerProcessImpl( @@ -54,17 +54,17 @@ proc schedPeerProcessImpl( ## let run = buddy.run trace info & ": begin", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short, + frameID=instr.frameIdStr, peer=($buddy.peer), peerID=buddy.peerID.short, syncState=instr.syncState # Activate peer buddy.run.nPeers.inc discard await run.worker.schedPeer(buddy) - buddy.processFinished(instr, info) + buddy.processFinishedClearFrame(instr, info) trace info & ": end", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short, + frameID=instr.frameIdStr, peer=($buddy.peer), peerID=buddy.peerID.short, syncState=instr.syncState # ------------------------------------------------------------------------------ @@ -105,11 +105,11 @@ proc schedStartWorker*( const info = instr.replayLabel() let - buddy = run.newPeer(instr, info) + buddy = run.newPeer(instr, info).valueOr: return accept = run.worker.schedStart(buddy) trace info & ": begin", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + peer=($buddy.peer), peerID=buddy.peerID.short if accept != instr.accept: warn info & ": result argument differs", n=run.iNum, serial=instr.serial, @@ -122,7 +122,7 @@ proc schedStartWorker*( buddy.delPeer(info) # Clean up trace info & ": end", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + peer=($buddy.peer), peerID=buddy.peerID.short proc schedStopWorker*( @@ -132,7 +132,7 @@ proc schedStopWorker*( ## Runs `schedStop()` in the foreground. ## const info = instr.replayLabel() - let buddy = run.getOrNewPeerFrame(instr, info) + let buddy = run.getOrNewPeerFrame(instr, info).valueOr: return run.worker.schedStop(buddy) # As the `schedStop()` function environment was captured only after the @@ -141,7 +141,10 @@ proc schedStopWorker*( # which has its desciptor sort of unintialised (relative to `instr`.) if not buddy.isNew: # Syncer state was captured when leaving the `schedStop()` handler. - if instr.peerCtrl == Stopped and not buddy.ctrl.stopped: + if instr.peerCtx.isNone(): + warn info & ": peer ctx missing", n=run.iNum, serial=instr.serial + return + if instr.peerCtx.value.peerCtrl == Stopped and not buddy.ctrl.stopped: buddy.ctrl.stopped = true buddy.checkSyncerState(instr, info) @@ -149,7 +152,7 @@ proc schedStopWorker*( buddy.delPeer(info) trace info & ": done", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + peer=($buddy.peer), peerID=buddy.peerID.short proc schedPoolWorker*( @@ -159,7 +162,7 @@ proc schedPoolWorker*( ## Runs `schedPool()` in the foreground. ## const info = instr.replayLabel() - let buddy = run.getOrNewPeerFrame(instr, info) + let buddy = run.getOrNewPeerFrame(instr, info).valueOr: return if 0 < run.nPeers: warn info & ": no active peers allowed", n=run.iNum, serial=instr.serial, @@ -173,12 +176,10 @@ proc schedPoolWorker*( # Syncer state was captured when leaving the `schedPool()` handler. buddy.checkSyncerState(instr, info) - - # Reset frame data - buddy.frameID = 0 + buddy.processFinishedClearFrame(instr, info) info info & ": done", n=run.iNum, serial=instr.serial, - frameID=instr.frameID.idStr, peer=($buddy.peer), peerID=buddy.peerID.short + peer=($buddy.peer), peerID=buddy.peerID.short proc schedPeerBegin*( @@ -189,7 +190,7 @@ proc schedPeerBegin*( ## # Synchronise against captured environment and start process const info = instr.replayLabel() - let buddy = run.getOrNewPeerFrame(instr, info) + let buddy = run.getOrNewPeerFrame(instr, info).valueOr: return discard await buddy.waitForSyncedEnv(instr, info) asyncSpawn buddy.schedPeerProcessImpl(instr, info) diff --git a/tools/syncer/trace/trace_desc.nim b/tools/syncer/trace/trace_desc.nim index 91de105df2..9d3cd0b4d2 100644 --- a/tools/syncer/trace/trace_desc.nim +++ b/tools/syncer/trace/trace_desc.nim @@ -28,7 +28,7 @@ export worker_desc const - TraceVersionID* = 20250915 + TraceVersionID* = 20250917 TraceSetupID* = 1 ## Phase 1 layout ID, prepare TraceRunnerID* = 10 ## Phase 2 layout ID, full execution @@ -75,36 +75,45 @@ type ImportBlock SyncBlock + TraceHdrUnproc* = object + ## Optional sub-object for `TraceRecBase` + hLen*: uint64 ## # unprocessed header entries + hChunks*: uint ## # unprocessed header iv segments + hLast*: BlockNumber ## last avail block number + hLastLen*: uint64 ## size of last block number interval + + TraceBlkUnproc* = object + ## Optional sub-object for `TraceRecBase` + bLen*: uint64 ## # unprocessed block entries + bChunks*: uint ## # unprocessed block iv segments + bLeast*: BlockNumber ## least avail block number + bLeastLen*: uint64 ## size of first interval + + TracePeerCtx* = object + ## Optional sub-object for `TraceRecBase` + peerCtrl*: BuddyRunState ## Sync peer run state + peerID*: Hash ## Sync peer ID (if any) + nHdrErrors*: uint8 ## Header tranfer errors + nBlkErrors*: uint8 ## Body tranfer errors + TraceRecBase* = object of RootObj ## Trace context applicable with and without known peer time*: Duration ## Relative to `TraceRef.started` - serial*: uint ## Increasing serial number - frameID*: uint ## Begin/end frame - nPeers*: uint - syncState*: SyncState - chainMode*: HeaderChainMode - poolMode*: bool + serial*: uint ## Capture record ID + frameID*: Opt[uint] ## Begin/end frame for scheduler tasks + nPeers*: uint ## Number of sync peers (buddies) + syncState*: SyncState ## Headers/bodies preocessing state + chainMode*: HeaderChainMode ## Headers cache/DB state + poolMode*: bool ## Mostly implied by `syncState` baseNum*: BlockNumber ## Max finalised number from `FC` module latestNum*: BlockNumber ## Number of latest branch head antecedent*: BlockNumber ## Lower end of header chain cache - hdrUnprLen*: uint64 ## # unprocessed header entries - hdrUnprChunks*: uint ## # unprocessed header iv segments - hdrUnprLast*: BlockNumber ## last avail block number - hdrUnprLastLen*: uint64 ## size of last block number interval - - blkUnprLen*: uint64 ## # unprocessed block entries - blkUnprChunks*: uint ## # unprocessed block iv segments - blkUnprLeast*: BlockNumber ## least avail block number - blkUnprLeastLen*: uint64 ## size of first interval - - stateAvail*: int ## Bitmask: 1=peerCtrl, 2=peerID, etc. - peerCtrl*: BuddyRunState ## 1) Rlp encoded `Opt[seq[xxx]]` would - peerID*: Hash ## 2) .. need manual decoder/reader - nHdrErrors*: uint8 ## 4) # header comm. errors - nBlkErrors*: uint8 ## 8) # body comm. errors - slowPeer*: Hash ## 16) Registered slow peer + hdrUnpr*: Opt[TraceHdrUnproc] ## Optional unprocessed headers state + blkUnpr*: Opt[TraceBlkUnproc] ## Optional unprocessed blocks state + peerCtx*: Opt[TracePeerCtx] ## Sync peer specific ctx + slowPeer*: Opt[Hash] ## Registered slow peer TraceVersionInfo* = object of TraceRecBase version*: uint @@ -163,9 +172,8 @@ type ## Environment is captured after the `getBlockHeaders()` handler is run. req*: BlockHeadersRequest ## Fetch request ivReq*: BnRange ## Request as interval of block numbers - fieldAvail*: uint ## Bitmask: 1=fetched, 2=error - fetched*: FetchHeadersData ## If dowloaded successfully - error*: BeaconError + fetched*: Opt[FetchHeadersData] ## If dowloaded successfully + error*: Opt[BeaconError] TraceSyncHeaders* = object of TraceRecBase ## Environment is captured when the `syncBlockHeaders()` handler is run. @@ -175,9 +183,8 @@ type ## Environment is captured after the `getBlockBodies()` handler is run. req*: BlockBodiesRequest ## Fetch request ivReq*: BnRange ## Request as interval of block numbers - fieldAvail*: uint ## Bitmask: 1=fetchd, 2=error - fetched*: FetchBodiesData ## If dowloaded successfully - error*: BeaconError + fetched*: Opt[FetchBodiesData] ## If dowloaded successfully + error*: Opt[BeaconError] TraceSyncBodies* = object of TraceRecBase ## Environment is captured when the `syncBlockBodies()` handler is run. @@ -187,9 +194,8 @@ type ## Environment is captured after the `importBlock()` handler is run. ethBlock*: EthBlock ## Request argument effPeerID*: Hash ## Request argument - fieldAvail*: uint ## Bitmask: 1=elapsed, 2=error - elapsed*: Duration ## Processing time on success - error*: BeaconError + elapsed*: Opt[Duration] ## Processing time on success + error*: Opt[BeaconError] TraceSyncBlock* = object of TraceRecBase ## Environment is captured after the `syncImportBlock()` handler is run. diff --git a/tools/syncer/trace/trace_setup/setup_blocks.nim b/tools/syncer/trace/trace_setup/setup_blocks.nim index 4afa4e70be..50a21afc62 100644 --- a/tools/syncer/trace/trace_setup/setup_blocks.nim +++ b/tools/syncer/trace/trace_setup/setup_blocks.nim @@ -63,11 +63,9 @@ proc fetchBodiesTrace*( tRec.req = req tRec.ivReq = ivReq if data.isOk: - tRec.fieldAvail = 1 - tRec.fetched = data.value + tRec.fetched = Opt.some(data.value) else: - tRec.fieldAvail = 2 - tRec.error = data.error + tRec.error = Opt.some(data.error) buddy.traceWrite tRec trace "=BodiesFetch", peer=($buddy.peer), peerID=buddy.peerID.short, @@ -103,15 +101,13 @@ proc importBlockTrace*( tRec.ethBlock = ethBlock tRec.effPeerID = effPeerID if data.isOk: - tRec.fieldAvail = 1 - tRec.elapsed = data.value + tRec.elapsed = Opt.some(data.value) else: - tRec.fieldAvail = 2 - tRec.error = data.error + tRec.error = Opt.some(data.error) buddy.traceWrite tRec trace "=BlockImport", peer=($buddy.peer), peerID=buddy.peerID.short, - effPeerID=tRec.peerID.short, serial=tRec.serial + effPeerID=effPeerID.short, serial=tRec.serial return data proc syncBlockTrace*( @@ -123,7 +119,7 @@ proc syncBlockTrace*( buddy.traceWrite tRec trace "=BlockSync", peer=($buddy.peer), peerID=buddy.peerID.short, - effPeerID=tRec.peerID.short, serial=tRec.serial + serial=tRec.serial # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/trace/trace_setup/setup_headers.nim b/tools/syncer/trace/trace_setup/setup_headers.nim index 7089d1014d..2ce41a9ba1 100644 --- a/tools/syncer/trace/trace_setup/setup_headers.nim +++ b/tools/syncer/trace/trace_setup/setup_headers.nim @@ -41,11 +41,9 @@ proc fetchHeadersTrace*( tRec.init buddy tRec.req = req if data.isOk: - tRec.fieldAvail = 1 - tRec.fetched = data.value + tRec.fetched = Opt.some(data.value) else: - tRec.fieldAvail = 2 - tRec.error = data.error + tRec.error = Opt.some(data.error) buddy.traceWrite tRec trace "=HeadersFetch", peer=($buddy.peer), peerID=buddy.peerID.short, diff --git a/tools/syncer/trace/trace_setup/setup_helpers.nim b/tools/syncer/trace/trace_setup/setup_helpers.nim index 13bf5cbfc8..75795784e6 100644 --- a/tools/syncer/trace/trace_setup/setup_helpers.nim +++ b/tools/syncer/trace/trace_setup/setup_helpers.nim @@ -12,7 +12,7 @@ import std/[strformat, strutils], - pkg/[chronos, stew/interval_set], + pkg/[chronos, results, stew/interval_set], ../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers, ../trace_desc @@ -38,25 +38,26 @@ proc init*(tb: var TraceRecBase; ctx: BeaconCtxRef) = tb.latestNum = ctx.chain.latestNumber tb.antecedent = ctx.hdrCache.antecedent.number - tb.hdrUnprChunks = ctx.hdr.unprocessed.chunks().uint - if 0 < tb.hdrUnprChunks: - tb.hdrUnprLen = ctx.hdr.unprocessed.total() + let hChunks = ctx.hdr.unprocessed.chunks().uint + if 0 < hChunks: let iv = ctx.hdr.unprocessed.le().expect "valid iv" - tb.hdrUnprLast = iv.maxPt - tb.hdrUnprLastLen = iv.len + tb.hdrUnpr = Opt.some(TraceHdrUnproc( + hChunks: hChunks, + hLen: ctx.hdr.unprocessed.total(), + hLast: iv.maxPt, + hLastLen: iv.len)) - tb.blkUnprChunks = ctx.blk.unprocessed.chunks().uint - if 0 < tb.blkUnprChunks: - tb.blkUnprLen = ctx.blk.unprocessed.total() + let bChunks = ctx.blk.unprocessed.chunks().uint + if 0 < bChunks: let iv = ctx.blk.unprocessed.ge().expect "valid iv" - tb.blkUnprLeast = iv.minPt - tb.blkUnprLeastLen = iv.len + tb.blkUnpr = Opt.some(TraceBlkUnproc( + bChunks: bChunks, + bLen: ctx.blk.unprocessed.total(), + bLeast: iv.minPt, + bLeastLen: iv.len)) + + tb.slowPeer = ctx.pool.lastSlowPeer - if ctx.pool.lastSlowPeer.isOk(): - tb.stateAvail = 16 - tb.slowPeer = ctx.pool.lastSlowPeer.value - else: - tb.stateAvail = 0 proc init*(tb: var TraceRecBase; buddy: BeaconBuddyRef) = ## Variant of `init()` for `buddy` rather than `ctx` @@ -65,11 +66,11 @@ proc init*(tb: var TraceRecBase; buddy: BeaconBuddyRef) = trc = ctx.trace if not trc.isNil: tb.init ctx - tb.stateAvail += 15 - tb.peerCtrl = buddy.ctrl.state - tb.peerID = buddy.peerID - tb.nHdrErrors = buddy.only.nRespErrors.hdr - tb.nBlkErrors = buddy.only.nRespErrors.blk + tb.peerCtx = Opt.some(TracePeerCtx( + peerCtrl: buddy.ctrl.state, + peerID: buddy.peerID, + nHdrErrors: buddy.only.nRespErrors.hdr, + nBlkErrors: buddy.only.nRespErrors.blk)) # ------------------------------------------------------------------------------ # Public helpers diff --git a/tools/syncer/trace/trace_setup/setup_sched.nim b/tools/syncer/trace/trace_setup/setup_sched.nim index 6b69168b5a..d2559c0539 100644 --- a/tools/syncer/trace/trace_setup/setup_sched.nim +++ b/tools/syncer/trace/trace_setup/setup_sched.nim @@ -48,25 +48,25 @@ proc schedDaemonTrace*( ## var tBeg: TraceSchedDaemonBegin tBeg.init ctx - tBeg.frameID = tBeg.serial + tBeg.frameID = Opt.some(tBeg.serial) ctx.traceWrite tBeg - trace "+Daemon", serial=tBeg.serial, frameID=tBeg.frameID.idStr, + trace "+Daemon", serial=tBeg.serial, frameID=tBeg.frameID.value.idStr, syncState=tBeg.syncState let idleTime = await ctx.trace.backup.schedDaemon ctx var tEnd: TraceSchedDaemonEnd tEnd.init ctx - tEnd.frameID = tBeg.serial # refers back to `tBeg` capture + tEnd.frameID = Opt.some(tBeg.serial) # refers back to `tBeg` capture tEnd.idleTime = idleTime ctx.traceWrite tEnd if 0 < tEnd.serial: - trace "-Daemon", serial=tEnd.serial, frameID=tEnd.frameID.idStr, + trace "-Daemon", serial=tEnd.serial, frameID=tEnd.frameID.value.idStr, syncState=tBeg.syncState, idleTime=idleTime.toStr else: - trace "-Daemon (blind)", serial="n/a", frameID=tEnd.frameID.idStr, + trace "-Daemon (blind)", serial="n/a", frameID=tEnd.frameID.value.idStr, syncState=tBeg.syncState, idleTime=idleTime.toStr return idleTime @@ -82,14 +82,14 @@ proc schedStartTrace*(buddy: BeaconBuddyRef): bool = if not ctx.hibernate: var tRec: TraceSchedStart tRec.init buddy - tRec.frameID = tRec.serial + tRec.frameID = Opt.some(tRec.serial) tRec.peerIP = buddy.getIP() tRec.peerPort = buddy.getPort() tRec.accept = acceptOk buddy.traceWrite tRec trace "=StartPeer", peer=($buddy.peer), peerID=buddy.peerID.short, - serial=tRec.serial, frameID=tRec.frameID.idStr, + serial=tRec.serial, frameID=tRec.frameID.value.idStr, syncState=tRec.syncState acceptOk @@ -105,13 +105,13 @@ proc schedStopTrace*(buddy: BeaconBuddyRef) = if not ctx.hibernate: var tRec: TraceSchedStop tRec.init buddy - tRec.frameID = tRec.serial + tRec.frameID = Opt.some(tRec.serial) tRec.peerIP = buddy.getIP() tRec.peerPort = buddy.getPort() buddy.traceWrite tRec trace "=StopPeer", peer=($buddy.peer), peerID=buddy.peerID.short, - serial=tRec.serial, frameID=tRec.frameID.idStr, + serial=tRec.serial, frameID=tRec.frameID.value.idStr, syncState=tRec.syncState @@ -122,7 +122,7 @@ proc schedPoolTrace*(buddy: BeaconBuddyRef; last: bool; laps: int): bool = var tRec: TraceSchedPool tRec.init buddy - tRec.frameID = tRec.serial + tRec.frameID = Opt.some(tRec.serial) tRec.peerIP = buddy.getIP() tRec.peerPort = buddy.getPort() tRec.last = last @@ -131,7 +131,7 @@ proc schedPoolTrace*(buddy: BeaconBuddyRef; last: bool; laps: int): bool = buddy.traceWrite tRec trace "=Pool", peer=($buddy.peer), peerID=buddy.peerID.short, - serial=tRec.serial, frameID=tRec.frameID.idStr + serial=tRec.serial stopOk @@ -149,26 +149,27 @@ proc schedPeerTrace*( var tBeg: TraceSchedPeerBegin if noisy: tBeg.init buddy - tBeg.frameID = tBeg.serial + tBeg.frameID = Opt.some(tBeg.serial) tBeg.peerIP = buddy.getIP() tBeg.peerPort = buddy.getPort() buddy.traceWrite tBeg trace "+Peer", peer=($buddy.peer), peerID=buddy.peerID.short, - serial=tBeg.serial, frameID=tBeg.frameID.idStr, syncState=tBeg.syncState + serial=tBeg.serial, frameID=tBeg.frameID.value.idStr, + syncState=tBeg.syncState let idleTime = await ctx.trace.backup.schedPeer(buddy) if noisy: var tEnd: TraceSchedPeerEnd tEnd.init buddy - tEnd.frameID = tBeg.serial # refers back to `tBeg` capture + tEnd.frameID = Opt.some(tBeg.serial) # refers back to `tBeg` capture tEnd.idleTime = idleTime buddy.traceWrite tEnd trace "-Peer", peer=($buddy.peer), peerID=buddy.peerID.short, - serial=tEnd.serial, frameID=tEnd.frameID.idStr, syncState=tBeg.syncState, - idleTime=idleTime.toStr + serial=tEnd.serial, frameID=tEnd.frameID.value.idStr, + syncState=tBeg.syncState, idleTime=idleTime.toStr return idleTime From 457cfabc381bf180fd77bb7b85c4186ae40df70e Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Thu, 18 Sep 2025 11:46:59 +0100 Subject: [PATCH 15/18] Re-factor `ReplayRef` inheritable by `ReplayRunnerRef` --- tools/syncer/replay/replay_desc.nim | 11 +---- tools/syncer/replay/replay_runner.nim | 4 +- .../replay/replay_runner/runner_desc.nim | 13 +++--- .../replay/replay_runner/runner_dispatch.nim | 2 +- .../runner_dispatch/dispatch_blocks.nim | 10 ++--- .../runner_dispatch/dispatch_headers.nim | 2 +- .../runner_dispatch/dispatch_helpers.nim | 6 +-- .../runner_dispatch/dispatch_sched.nim | 12 +++--- .../runner_dispatch/dispatch_sync.nim | 2 +- .../runner_dispatch/dispatch_version.nim | 2 +- .../replay/replay_runner/runner_init.nim | 11 ++--- tools/syncer/replay/replay_setup.nim | 42 +++++++++---------- 12 files changed, 51 insertions(+), 66 deletions(-) diff --git a/tools/syncer/replay/replay_desc.nim b/tools/syncer/replay/replay_desc.nim index b4bab66fb5..7bb1d03ad7 100644 --- a/tools/syncer/replay/replay_desc.nim +++ b/tools/syncer/replay/replay_desc.nim @@ -14,14 +14,11 @@ import std/streams, - pkg/chronos, ../trace/trace_desc, - ./replay_reader/reader_desc, - ./replay_runner/runner_desc + ./replay_reader/reader_desc export reader_desc, - runner_desc, trace_desc const @@ -67,7 +64,6 @@ type stopQuit*: bool ## Quit after replay backup*: BeaconHandlersRef ## Can restore previous handlers reader*: ReplayReaderRef ## Input records - runner*: ReplayRunnerRef ## Replay descriptor ReplayPayloadRef* = ref object of RootRef @@ -137,11 +133,6 @@ type # Public helpers # ------------------------------------------------------------------------------ -func replay*(ctx: BeaconCtxRef): ReplayRef = - ## Getter, get replay descriptor (if any) - if ctx.handler.version == ReplayRunnerID: - return ctx.handler.ReplayRef - template replayLabel*(w: untyped): string = ## Static getter, retrieve replay type label ReplayTypeLabel[(typeof w).toTraceRecType] diff --git a/tools/syncer/replay/replay_runner.nim b/tools/syncer/replay/replay_runner.nim index 08b761bb38..0290fb0c2f 100644 --- a/tools/syncer/replay/replay_runner.nim +++ b/tools/syncer/replay/replay_runner.nim @@ -14,8 +14,8 @@ import pkg/chronos, - ./replay_runner/runner_dispatch, - ./[replay_desc, replay_reader] + ./replay_runner/[runner_desc, runner_dispatch], + ./replay_reader # ------------------------------------------------------------------------------ # Public functions diff --git a/tools/syncer/replay/replay_runner/runner_desc.nim b/tools/syncer/replay/replay_runner/runner_desc.nim index c4c86e6557..9e0d2c9299 100644 --- a/tools/syncer/replay/replay_runner/runner_desc.nim +++ b/tools/syncer/replay/replay_runner/runner_desc.nim @@ -19,7 +19,11 @@ import ../../../../execution_chain/networking/p2p, ../../../../execution_chain/sync/wire_protocol, ../../../../execution_chain/sync/beacon/worker/worker_desc, - ../../trace/trace_desc + ../../trace/trace_desc, + ../replay_desc + +export + replay_desc const replayWaitForCompletion* = chronos.nanoseconds(100) @@ -95,10 +99,8 @@ type capa*: Dispatcher ## Cabability `eth68`, `eth69`, etc. prots*: array[MAX_PROTOCOLS,RootRef] ## `capa` init flags, protocol states - ReplayRunnerRef* = ref object + ReplayRunnerRef* = ref object of ReplayRef # Global state - ctx*: BeaconCtxRef ## Beacon syncer descriptor - worker*: BeaconHandlersRef ## Refers to original handlers table ethState*: ReplayEthState ## For ethxx compatibility stopRunner*: bool ## Shut down request nSessions*: int ## Numer of sessions left @@ -111,7 +113,6 @@ type # Instruction handling instrNumber*: uint ## Instruction counter - fakeImport*: bool ## No database import if `true` # ------------------------------------------------------------------------------ # Public helpers @@ -133,7 +134,7 @@ template toReplayMsgType*(trc: type): untyped = ReplaySyncBlockMsgRef else: {.error: "Unsupported trace record type".} - + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch.nim b/tools/syncer/replay/replay_runner/runner_dispatch.nim index c7b2b4daad..01a7e97705 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch.nim @@ -15,7 +15,7 @@ import pkg/[chronicles, chronos], ../../../../execution_chain/networking/p2p, - ../replay_desc, + ./runner_desc, ./runner_dispatch/[dispatch_blocks, dispatch_headers, dispatch_sched, dispatch_sync, dispatch_version] diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim index 2fb8cafba6..fbd141b875 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim @@ -15,7 +15,7 @@ import pkg/[chronicles, chronos, eth/common, stew/interval_set], ../../../../../execution_chain/sync/wire_protocol, - ../../replay_desc, + ../runner_desc, ./dispatch_helpers logScope: @@ -161,11 +161,9 @@ proc importBlockHandler*( ", expected=%" & instr.ethBlock.computeRlpHash.short data = instr - let - ctx = buddy.run.ctx - rpl = ctx.replay - if not rpl.runner.fakeImport: - let rc = await rpl.backup.importBlock(buddy, ethBlock, effPeerID) + let run = buddy.run + if not run.fakeImport: + let rc = await run.backup.importBlock(buddy, ethBlock, effPeerID) if rc.isErr or data.error.isSome(): const info = info & ": result values differ" let serial = data.serial diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim index 286741b04b..f8a1279d78 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim @@ -15,7 +15,7 @@ import pkg/[chronicles, chronos, eth/common], ../../../../../execution_chain/sync/wire_protocol, - ../../replay_desc, + ../runner_desc, ./dispatch_helpers logScope: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim index 05d8866d5f..5d25b6ea71 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim @@ -19,7 +19,7 @@ import ../../../../../execution_chain/sync/wire_protocol, ../../../../../execution_chain/sync/beacon/worker/helpers as worker_helpers, ../../../trace/trace_setup/setup_helpers as trace_helpers, - ../../replay_desc + ../runner_desc export trace_helpers.idStr, @@ -355,11 +355,9 @@ proc peerStatesDifferImpl( # Public functions # ------------------------------------------------------------------------------ -func iNum*(desc: ReplayInstance|ReplayRunnerRef|BeaconCtxRef): uint = +func iNum*(desc: ReplayInstance|ReplayRunnerRef): uint = when desc is ReplayRunnerRef: desc.instrNumber - elif desc is BeaconCtxRef: - desc.replay.runner.instrNumber else: desc.run.instrNumber diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim index aa4e661f08..b75976a851 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim @@ -15,7 +15,7 @@ import std/tables, pkg/[chronicles, chronos, eth/common], - ../../replay_desc, + ../runner_desc, ./dispatch_helpers logScope: @@ -37,7 +37,7 @@ proc schedDaemonProcessImpl( trace info & ": begin", n=run.iNum, serial=instr.serial, frameID=instr.frameIdStr, syncState=instr.syncState - discard await run.worker.schedDaemon(run.ctx) + discard await run.backup.schedDaemon(run.ctx) daemon.processFinishedClearFrame(instr, info) trace info & ": end", n=run.iNum, serial=instr.serial, @@ -60,7 +60,7 @@ proc schedPeerProcessImpl( # Activate peer buddy.run.nPeers.inc - discard await run.worker.schedPeer(buddy) + discard await run.backup.schedPeer(buddy) buddy.processFinishedClearFrame(instr, info) trace info & ": end", n=run.iNum, serial=instr.serial, @@ -106,7 +106,7 @@ proc schedStartWorker*( info = instr.replayLabel() let buddy = run.newPeer(instr, info).valueOr: return - accept = run.worker.schedStart(buddy) + accept = run.backup.schedStart(buddy) trace info & ": begin", n=run.iNum, serial=instr.serial, peer=($buddy.peer), peerID=buddy.peerID.short @@ -133,7 +133,7 @@ proc schedStopWorker*( ## const info = instr.replayLabel() let buddy = run.getOrNewPeerFrame(instr, info).valueOr: return - run.worker.schedStop(buddy) + run.backup.schedStop(buddy) # As the `schedStop()` function environment was captured only after the # syncer was activated, there might still be some unregistered peers hanging @@ -172,7 +172,7 @@ proc schedPoolWorker*( # `schedPool()` function. run.ctx.poolMode = false - discard run.worker.schedPool(buddy, instr.last, instr.laps.int) + discard run.backup.schedPool(buddy, instr.last, instr.laps.int) # Syncer state was captured when leaving the `schedPool()` handler. buddy.checkSyncerState(instr, info) diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim index 67956eb0fc..9db8ef2e7b 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim @@ -15,7 +15,7 @@ import pkg/chronicles, ../../../../../execution_chain/core/chain, - ../../replay_desc, + ../runner_desc, ./dispatch_helpers logScope: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim index 6baafb0d99..08e194bccb 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim @@ -15,7 +15,7 @@ import pkg/chronicles, ../../../../../execution_chain/core/chain, - ../../replay_desc, + ../runner_desc, ./dispatch_helpers logScope: diff --git a/tools/syncer/replay/replay_runner/runner_init.nim b/tools/syncer/replay/replay_runner/runner_init.nim index 9b452d0661..932c8c42fe 100644 --- a/tools/syncer/replay/replay_runner/runner_init.nim +++ b/tools/syncer/replay/replay_runner/runner_init.nim @@ -16,7 +16,7 @@ import pkg/chronos, ../../../../execution_chain/networking/[p2p, p2p_peers, peer_pool], ../../../../execution_chain/sync/wire_protocol, - ../replay_desc + ./runner_desc logScope: topics = "replay" @@ -58,7 +58,7 @@ proc init(T: type ReplayEthState): T = # Public constructor(s) # ------------------------------------------------------------------------------ -proc init*(T: type ReplayRunnerRef; rpl: ReplayRef): T = +proc initRunner*(rpl: ReplayRunnerRef) = ## Initialise dispatcher const info = "ReplayRunnerRef(): " if ReplayRunnerID != rpl.ctx.handler.version: @@ -66,13 +66,10 @@ proc init*(T: type ReplayRunnerRef; rpl: ReplayRef): T = handlerVersion=rpl.ctx.handler.version quit(QuitFailure) - T(ctx: rpl.ctx, - worker: rpl.backup, - ethState: ReplayEthState.init(), - fakeImport: rpl.fakeImport) + rpl.ethState = ReplayEthState.init() -proc destroy*(run: ReplayRunnerRef) = +proc destroyRunner*(run: ReplayRunnerRef) = discard # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_setup.nim b/tools/syncer/replay/replay_setup.nim index 33f38f9d04..ff13d777e0 100644 --- a/tools/syncer/replay/replay_setup.nim +++ b/tools/syncer/replay/replay_setup.nim @@ -18,8 +18,8 @@ import ../../../execution_chain/sync/wire_protocol, ./replay_reader/reader_init, ./replay_runner/runner_dispatch/[dispatch_blocks, dispatch_headers], - ./replay_runner/runner_init, - ./[replay_desc, replay_runner] + ./replay_runner/[runner_desc, runner_init], + ./replay_runner logScope: topics = "beacon replay" @@ -73,14 +73,13 @@ proc noOpSchedPeer(buddy: BeaconBuddyRef): # Private functions # ------------------------------------------------------------------------------ -proc checkStop(rpl: ReplayRef): ReplayStopIfFn = +proc checkStop(rpl: ReplayRunnerRef): ReplayStopIfFn = proc(): bool = - if rpl.runner.isNil or - rpl.runner.stopRunner: + if rpl.stopRunner: return true false -proc cleanUp(rpl: ReplayRef): ReplayEndUpFn = +proc cleanUp(rpl: ReplayRunnerRef): ReplayEndUpFn = proc() = rpl.stopSync(rpl) if rpl.stopQuit: @@ -91,16 +90,14 @@ proc cleanUp(rpl: ReplayRef): ReplayEndUpFn = # -------------- -proc replayStartCB(rpl: ReplayRef) = +proc replayStartCB(rpl: ReplayRunnerRef) = ## Start replay emulator ## - rpl.reader = ReplayReaderRef.init(rpl.captStrm) - rpl.runner = ReplayRunnerRef.init(rpl) - # Set up redirect handlers for replay rpl.version = ReplayRunnerID # activate # use as is # suspend # use as is + rpl.reader = ReplayReaderRef.init(rpl.captStrm) rpl.schedDaemon = noOpSchedDaemon rpl.schedStart = noOpSchedStartFalse # `false` => don't register rpl.schedStop = noOpBuddy @@ -113,18 +110,20 @@ proc replayStartCB(rpl: ReplayRef) = rpl.importBlock = importBlockHandler # from dispatcher rpl.syncImportBlock = noOpBuddy + rpl.initRunner() + rpl.startSync = proc(self: BeaconHandlersSyncRef) = discard rpl.stopSync = proc(self: BeaconHandlersSyncRef) = - ReplayRef(self).reader.destroy() - ReplayRef(self).runner.destroy() + ReplayRunnerRef(self).reader.destroy() + ReplayRunnerRef(self).destroyRunner() stopInfo.onException(DontQuit): - ReplayRef(self).captStrm.close() - ReplayRef(self).ctx.pool.handlers = ReplayRef(self).backup + ReplayRunnerRef(self).captStrm.close() + ReplayRunnerRef(self).ctx.pool.handlers = ReplayRunnerRef(self).backup # Start fake scheduler - asyncSpawn rpl.runner.runDispatcher( + asyncSpawn rpl.runDispatcher( rpl.reader, stopIf=rpl.checkStop, endUp=rpl.cleanUp) # ------------------------------------------------------------------------------ @@ -137,7 +136,8 @@ proc replaySetup*( noStopQuit: bool; fakeImport: bool; ): Result[void,string] = - ## .. + ## setup replay emulator + ## const info = "replaySetup(): " if ctx.handler.version != 0: @@ -149,7 +149,7 @@ proc replaySetup*( return err("Cannot open trace file for reading" & ", fileName=\"" & fileName & "\"") - let rpl = ReplayRef( + let rpl = ReplayRunnerRef( ctx: ctx, captStrm: strm, fakeImport: fakeImport, @@ -174,12 +174,12 @@ proc replaySetup*( syncImportBlock: ctx.handler.syncImportBlock) rpl.startSync = proc(self: BeaconHandlersSyncRef) = - ReplayRef(self).replayStartCB() + ReplayRunnerRef(self).replayStartCB() rpl.stopSync = proc(self: BeaconHandlersSyncRef) = info.onException(DontQuit): - ReplayRef(self).captStrm.close() - ReplayRef(self).ctx.pool.handlers = ReplayRef(self).backup + ReplayRunnerRef(self).captStrm.close() + ReplayRunnerRef(self).ctx.pool.handlers = ReplayRunnerRef(self).backup ctx.pool.handlers = rpl ok() @@ -188,7 +188,7 @@ proc replaySetup*( proc replayRelease*(ctx: BeaconCtxRef) = ## Stop replay and restore descriptors if ctx.pool.handlers.version in {ReplaySetupID, ReplayRunnerID}: - ReplayRef(ctx.pool.handlers).stopSync(nil) + ReplayRunnerRef(ctx.pool.handlers).stopSync(nil) # ------------------------------------------------------------------------------ # End From 9c257492b371c498028364c442dcad1368719238 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Wed, 17 Sep 2025 17:25:06 +0100 Subject: [PATCH 16/18] Remove `ReplayMsgRef` types and use `ReplayPayloadRef` instead why The layout of the `ReplayMsgRef` sub-structures mirrors that of the `ReplayPayloadRef` sub-structures. So the latter sub-structures have been replaced by the former ones. --- tools/syncer/replay/replay_desc.nim | 36 +- .../replay/replay_reader/reader_reclog.nim | 196 +++++------ .../replay/replay_reader/reader_unpack.nim | 4 +- .../replay/replay_runner/runner_desc.nim | 55 +-- .../replay/replay_runner/runner_dispatch.nim | 34 +- .../runner_dispatch/dispatch_blocks.nim | 82 ++--- .../runner_dispatch/dispatch_headers.nim | 28 +- .../runner_dispatch/dispatch_helpers.nim | 313 +++++++++--------- .../runner_dispatch/dispatch_sched.nim | 61 ++-- .../runner_dispatch/dispatch_sync.nim | 29 +- .../runner_dispatch/dispatch_version.nim | 23 +- 11 files changed, 403 insertions(+), 458 deletions(-) diff --git a/tools/syncer/replay/replay_desc.nim b/tools/syncer/replay/replay_desc.nim index 7bb1d03ad7..732fdd8b69 100644 --- a/tools/syncer/replay/replay_desc.nim +++ b/tools/syncer/replay/replay_desc.nim @@ -71,63 +71,63 @@ type recType*: TraceRecType ReplayVersionInfo* = ref object of ReplayPayloadRef - data*: TraceVersionInfo + bag*: TraceVersionInfo # ------------- ReplaySyncActvFailed* = ref object of ReplayPayloadRef - data*: TraceSyncActvFailed + bag*: TraceSyncActvFailed ReplaySyncActivated* = ref object of ReplayPayloadRef - data*: TraceSyncActivated + bag*: TraceSyncActivated ReplaySyncHibernated* = ref object of ReplayPayloadRef - data*: TraceSyncHibernated + bag*: TraceSyncHibernated # ------------- ReplaySchedDaemonBegin* = ref object of ReplayPayloadRef - data*: TraceSchedDaemonBegin + bag*: TraceSchedDaemonBegin ReplaySchedDaemonEnd* = ref object of ReplayPayloadRef - data*: TraceSchedDaemonEnd + bag*: TraceSchedDaemonEnd ReplaySchedStart* = ref object of ReplayPayloadRef - data*: TraceSchedStart + bag*: TraceSchedStart ReplaySchedStop* = ref object of ReplayPayloadRef - data*: TraceSchedStop + bag*: TraceSchedStop ReplaySchedPool* = ref object of ReplayPayloadRef - data*: TraceSchedPool + bag*: TraceSchedPool ReplaySchedPeerBegin* = ref object of ReplayPayloadRef - data*: TraceSchedPeerBegin + bag*: TraceSchedPeerBegin ReplaySchedPeerEnd* = ref object of ReplayPayloadRef - data*: TraceSchedPeerEnd + bag*: TraceSchedPeerEnd # ------------- ReplayFetchHeaders* = ref object of ReplayPayloadRef - data*: TraceFetchHeaders + bag*: TraceFetchHeaders ReplaySyncHeaders* = ref object of ReplayPayloadRef - data*: TraceSyncHeaders + bag*: TraceSyncHeaders ReplayFetchBodies* = ref object of ReplayPayloadRef - data*: TraceFetchBodies + bag*: TraceFetchBodies ReplaySyncBodies* = ref object of ReplayPayloadRef - data*: TraceSyncBodies + bag*: TraceSyncBodies ReplayImportBlock* = ref object of ReplayPayloadRef - data*: TraceImportBlock + bag*: TraceImportBlock ReplaySyncBlock* = ref object of ReplayPayloadRef - data*: TraceSyncBlock + bag*: TraceSyncBlock # ------------------------------------------------------------------------------ # Public helpers @@ -135,7 +135,7 @@ type template replayLabel*(w: untyped): string = ## Static getter, retrieve replay type label - ReplayTypeLabel[(typeof w).toTraceRecType] + ReplayTypeLabel[(typeof w.bag).toTraceRecType] # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/replay/replay_reader/reader_reclog.nim b/tools/syncer/replay/replay_reader/reader_reclog.nim index 5bcc652027..09009dab48 100644 --- a/tools/syncer/replay/replay_reader/reader_reclog.nim +++ b/tools/syncer/replay/replay_reader/reader_reclog.nim @@ -94,167 +94,173 @@ func toStrOops(n: int): seq[string] = # ----------- -func toStrSeq(n: int; w: TraceVersionInfo): seq[string] = +func toStrSeq(n: int; w: ReplayVersionInfo): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) - let moan = if w.version < TraceVersionID: "(<" & $TraceVersionID & ")" - elif TraceVersionID < w.version: "(>" & $TraceVersionID & ")" + res.addX(w.replayLabel, n, w.bag) + let moan = if w.bag.version < TraceVersionID: "(<" & $TraceVersionID & ")" + elif TraceVersionID < w.bag.version: "(>" & $TraceVersionID & ")" else: "" - res.add "version=" & $w.version & moan - res.add "network=" & $w.networkId - res.add "base=" & w.baseNum.bnStr - res.add "latest=" & w.latestNum.bnStr + res.add "version=" & $w.bag.version & moan + res.add "network=" & $w.bag.networkId + res.add "base=" & w.bag.baseNum.bnStr + res.add "latest=" & w.bag.latestNum.bnStr res # ----------- -func toStrSeq(n: int; w: TraceSyncActvFailed): seq[string] = +func toStrSeq(n: int; w: ReplaySyncActvFailed): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) - res.add "base=" & w.baseNum.bnStr - res.add "latest=" & w.latestNum.bnStr + res.addX(w.replayLabel, n, w.bag) + res.add "base=" & w.bag.baseNum.bnStr + res.add "latest=" & w.bag.latestNum.bnStr res -func toStrSeq(n: int; w: TraceSyncActivated): seq[string] = +func toStrSeq(n: int; w: ReplaySyncActivated): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "head=" & w.head.bnStr - res.add "finHash=" & w.finHash.short - res.add "base=" & w.baseNum.bnStr - res.add "latest=" & w.latestNum.bnStr + res.addX(w.replayLabel, n, w.bag) + res.add "head=" & w.bag.head.bnStr + res.add "finHash=" & w.bag.finHash.short + res.add "base=" & w.bag.baseNum.bnStr + res.add "latest=" & w.bag.latestNum.bnStr res -func toStrSeq(n: int; w: TraceSyncHibernated): seq[string] = +func toStrSeq(n: int; w: ReplaySyncHibernated): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) - res.add "base=" & w.baseNum.bnStr - res.add "latest=" & w.latestNum.bnStr + res.addX(w.replayLabel, n, w.bag) + res.add "base=" & w.bag.baseNum.bnStr + res.add "latest=" & w.bag.latestNum.bnStr res # ----------- -func toStrSeq(n: int; w: TraceSchedDaemonBegin): seq[string] = +func toStrSeq(n: int; w: ReplaySchedDaemonBegin): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res -func toStrSeq(n: int; w: TraceSchedDaemonEnd): seq[string] = +func toStrSeq(n: int; w: ReplaySchedDaemonEnd): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res -func toStrSeq(n: int; w: TraceSchedStart): seq[string] = +func toStrSeq(n: int; w: ReplaySchedStart): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "peer=" & $w.peerIP & ":" & $w.peerPort - if not w.accept: + res.addX(w.replayLabel, n, w.bag) + res.add "peer=" & $w.bag.peerIP & ":" & $w.bag.peerPort + if not w.bag.accept: res.add "rejected" res -func toStrSeq(n: int; w: TraceSchedStop): seq[string] = +func toStrSeq(n: int; w: ReplaySchedStop): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "peer=" & $w.peerIP & ":" & $w.peerPort + res.addX(w.replayLabel, n, w.bag) + res.add "peer=" & $w.bag.peerIP & ":" & $w.bag.peerPort res -func toStrSeq(n: int; w: TraceSchedPool): seq[string] = +func toStrSeq(n: int; w: ReplaySchedPool): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "peer=" & $w.peerIP & ":" & $w.peerPort - res.add "last=" & $w.last - res.add "laps=" & $w.laps - res.add "stop=" & $w.stop + res.addX(w.replayLabel, n, w.bag) + res.add "peer=" & $w.bag.peerIP & ":" & $w.bag.peerPort + res.add "last=" & $w.bag.last + res.add "laps=" & $w.bag.laps + res.add "stop=" & $w.bag.stop res -func toStrSeq(n: int; w: TraceSchedPeerBegin): seq[string] = +func toStrSeq(n: int; w: ReplaySchedPeerBegin): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "peer=" & $w.peerIP & ":" & $w.peerPort + res.addX(w.replayLabel, n, w.bag) + res.add "peer=" & $w.bag.peerIP & ":" & $w.bag.peerPort res -func toStrSeq(n: int; w: TraceSchedPeerEnd): seq[string] = +func toStrSeq(n: int; w: ReplaySchedPeerEnd): seq[string] = var res = newSeqOfCap[string](15) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res # ----------- -func toStrSeq(n: int; w: TraceFetchHeaders): seq[string] = +func toStrSeq(n: int; w: ReplayFetchHeaders): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) let - rLen = w.req.maxResults - rRev = if w.req.reverse: "rev" else: "" - if w.req.startBlock.isHash: - res.add "req=" & w.req.startBlock.hash.short & "[" & $rLen & "]" & rRev + rLen = w.bag.req.maxResults + rRev = if w.bag.req.reverse: "rev" else: "" + if w.bag.req.startBlock.isHash: + res.add "req=" & + w.bag.req.startBlock.hash.short & "[" & $rLen & "]" & rRev else: - res.add "req=" & w.req.startBlock.number.bnStr & "[" & $rLen & "]" & rRev - if 0 < w.req.skip: - res.add "skip=" & $w.req.skip - if w.fetched.isSome(): - res.add "res=[" & $w.fetched.value.packet.headers.len & "]" - res.add "ela=" & w.fetched.value.elapsed.toStr - if w.error.isSome(): - if w.error.value.excp.ord == 0: + res.add "req=" & + w.bag.req.startBlock.number.bnStr & "[" & $rLen & "]" & rRev + if 0 < w.bag.req.skip: + res.add "skip=" & $w.bag.req.skip + if w.bag.fetched.isSome(): + res.add "res=[" & $w.bag.fetched.value.packet.headers.len & "]" + res.add "ela=" & w.bag.fetched.value.elapsed.toStr + if w.bag.error.isSome(): + if w.bag.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.value.excp).substr(1) - if w.error.value.msg.len != 0: - res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" - res.add "ela=" & w.error.value.elapsed.toStr + res.add "excp=" & ($w.bag.error.value.excp).substr(1) + if w.bag.error.value.msg.len != 0: + res.add "error=" & w.bag.error.value.name & + "(" & w.bag.error.value.msg & ")" + res.add "ela=" & w.bag.error.value.elapsed.toStr res -func toStrSeq(n: int; w: TraceSyncHeaders): seq[string] = +func toStrSeq(n: int; w: ReplaySyncHeaders): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res -func toStrSeq(n: int; w: TraceFetchBodies): seq[string] = +func toStrSeq(n: int; w: ReplayFetchBodies): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "req=" & w.ivReq.bnStr & "[" & $w.req.blockHashes.len & "]" - if w.fetched.isSome(): - res.add "res=[" & $w.fetched.value.packet.bodies.len & "]" - res.add "size=" & w.fetched.value.packet.bodies.getEncodedLength.uint64.toSI - res.add "ela=" & w.fetched.value.elapsed.toStr - if w.error.isSome(): - if w.error.value.excp.ord == 0: + res.addX(w.replayLabel, n, w.bag) + res.add "req=" & w.bag.ivReq.bnStr & "[" & $w.bag.req.blockHashes.len & "]" + if w.bag.fetched.isSome(): + res.add "res=[" & $w.bag.fetched.value.packet.bodies.len & "]" + res.add "size=" & + w.bag.fetched.value.packet.bodies.getEncodedLength.uint64.toSI + res.add "ela=" & w.bag.fetched.value.elapsed.toStr + if w.bag.error.isSome(): + if w.bag.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.value.excp).substr(1) - if w.error.value.msg.len != 0: - res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" - res.add "ela=" & w.error.value.elapsed.toStr + res.add "excp=" & ($w.bag.error.value.excp).substr(1) + if w.bag.error.value.msg.len != 0: + res.add "error=" & + w.bag.error.value.name & "(" & w.bag.error.value.msg & ")" + res.add "ela=" & w.bag.error.value.elapsed.toStr res -func toStrSeq(n: int; w: TraceSyncBodies): seq[string] = +func toStrSeq(n: int; w: ReplaySyncBodies): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res -func toStrSeq(n: int; w: TraceImportBlock): seq[string] = +func toStrSeq(n: int; w: ReplayImportBlock): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) - res.add "block=" & w.ethBlock.bnStr - res.add "size=" & w.ethBlock.getEncodedLength.uint64.toSI - res.add "effPeerID=" & w.effPeerID.short - if w.elapsed.isSome(): - res.add "ela=" & w.elapsed.value.toStr - if w.error.isSome(): - if w.error.value.excp.ord == 0: + res.addX(w.replayLabel, n, w.bag) + res.add "block=" & w.bag.ethBlock.bnStr + res.add "size=" & w.bag.ethBlock.getEncodedLength.uint64.toSI + res.add "effPeerID=" & w.bag.effPeerID.short + if w.bag.elapsed.isSome(): + res.add "ela=" & w.bag.elapsed.value.toStr + if w.bag.error.isSome(): + if w.bag.error.value.excp.ord == 0: res.add "failed" else: - res.add "excp=" & ($w.error.value.excp).substr(1) - if w.error.value.msg.len != 0: - res.add "error=" & w.error.value.name & "(" & w.error.value.msg & ")" - res.add "ela=" & w.error.value.elapsed.toStr + res.add "excp=" & ($w.bag.error.value.excp).substr(1) + if w.bag.error.value.msg.len != 0: + res.add "error=" & + w.bag.error.value.name & "(" & w.bag.error.value.msg & ")" + res.add "ela=" & w.bag.error.value.elapsed.toStr res -func toStrSeq(n: int; w: TraceSyncBlock): seq[string] = +func toStrSeq(n: int; w: ReplaySyncBlock): seq[string] = var res = newSeqOfCap[string](20) - res.addX(w.replayLabel, n, w) + res.addX(w.replayLabel, n, w.bag) res # ------------------------------------------------------------------------------ @@ -315,7 +321,7 @@ proc recLogToStrList*(pyl: ReplayPayloadRef; lnr = 0): seq[string] = when t == TraceRecType(0): lnr.toStrOops() else: - lnr.toStrSeq(pyl.T.data) + lnr.toStrSeq(pyl.T) pyl.recType.withReplayTypeExpr() diff --git a/tools/syncer/replay/replay_reader/reader_unpack.nim b/tools/syncer/replay/replay_reader/reader_unpack.nim index eee417b9e7..cc1f706862 100644 --- a/tools/syncer/replay/replay_reader/reader_unpack.nim +++ b/tools/syncer/replay/replay_reader/reader_unpack.nim @@ -240,8 +240,8 @@ proc getRecType(s: string; info: static[string]): TraceRecType = proc init(T: type; s: string; info: static[string]): T = (info & ".init()").onException(DontQuit): - var rec = Json.decode(s, JTraceRecord[typeof result.data]) - return T(recType: rec.kind, data: rec.bag) + var rec = Json.decode(s, JTraceRecord[typeof result.bag]) + return T(recType: rec.kind, bag: rec.bag) T(nil) # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_desc.nim b/tools/syncer/replay/replay_runner/runner_desc.nim index 9e0d2c9299..d94223837f 100644 --- a/tools/syncer/replay/replay_runner/runner_desc.nim +++ b/tools/syncer/replay/replay_runner/runner_desc.nim @@ -47,36 +47,6 @@ type name: string msg: string - # --------- internal data message types --------- - - ReplayMsgRef* = ref object of RootRef - ## Sub task context ## Identifies captured environment - recType*: TraceRecType ## Sub-type selector - - ReplayFetchHeadersMsgRef* = ref object of ReplayMsgRef - ## Headers fetch data message - instr*: TraceFetchHeaders ## Full context/environment - - ReplaySyncHeadersMsgRef* = ref object of ReplayMsgRef - ## Headers fetch sync message - instr*: TraceSyncHeaders ## Full context/environment - - ReplayFetchBodiesMsgRef* = ref object of ReplayMsgRef - ## Bodies fetch data message - instr*: TraceFetchBodies ## Full context/environment - - ReplaySyncBodiesMsgRef* = ref object of ReplayMsgRef - ## Bodies fetch sync message - instr*: TraceSyncBodies ## Full context/environment - - ReplayImportBlockMsgRef* = ref object of ReplayMsgRef - ## Block import data message - instr*: TraceImportBlock ## Full context/environment - - ReplaySyncBlockMsgRef* = ref object of ReplayMsgRef - ## Block import sync message - instr*: TraceSyncBlock ## Full context/environment - # --------- internal context types --------- ReplayBuddyRef* = ref object of BeaconBuddyRef @@ -84,13 +54,13 @@ type isNew*: bool ## Set in `getOrNewPeer()` when created run*: ReplayRunnerRef ## Back-reference for convenience frameID*: Opt[uint] ## Begin/end frame - message*: ReplayMsgRef ## Data message channel + message*: ReplayPayloadRef ## Data message channel ReplayDaemonRef* = ref object ## Daemeon job frame (similar to `ReplayBuddyRef`) run*: ReplayRunnerRef ## Back-reference for convenience frameID*: Opt[uint] ## Begin/end frame - message*: ReplayMsgRef ## Data message channel + message*: ReplayPayloadRef ## Data message channel # --------- @@ -114,27 +84,6 @@ type # Instruction handling instrNumber*: uint ## Instruction counter -# ------------------------------------------------------------------------------ -# Public helpers -# ------------------------------------------------------------------------------ - -template toReplayMsgType*(trc: type): untyped = - ## Derive replay record type from trace capture record type - when trc is TraceFetchHeaders: - ReplayFetchHeadersMsgRef - elif trc is TraceSyncHeaders: - ReplaySyncHeadersMsgRef - elif trc is TraceFetchBodies: - ReplayFetchBodiesMsgRef - elif trc is TraceSyncBodies: - ReplaySyncBodiesMsgRef - elif trc is TraceImportBlock: - ReplayImportBlockMsgRef - elif trc is TraceSyncBlock: - ReplaySyncBlockMsgRef - else: - {.error: "Unsupported trace record type".} - # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch.nim b/tools/syncer/replay/replay_runner/runner_dispatch.nim index 01a7e97705..b86c288658 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch.nim @@ -42,49 +42,49 @@ proc dispatch*( warn "dispatch(): Oops, unexpected void record", n=run.instrNumber of VersionInfo: - run.versionInfoWorker(pyl.ReplayVersionInfo.data) + run.versionInfoWorker(pyl.ReplayVersionInfo) of SyncActvFailed: - run.syncActvFailedWorker(pyl.ReplaySyncActvFailed.data) + run.syncActvFailedWorker(pyl.ReplaySyncActvFailed) of SyncActivated: - run.syncActivateWorker(pyl.ReplaySyncActivated.data) + run.syncActivateWorker(pyl.ReplaySyncActivated) of SyncHibernated: - run.syncSuspendWorker(pyl.ReplaySyncHibernated.data) + run.syncSuspendWorker(pyl.ReplaySyncHibernated) # Simple scheduler single run (no begin/end) functions of SchedStart: - run.schedStartWorker(pyl.ReplaySchedStart.data) + run.schedStartWorker(pyl.ReplaySchedStart) of SchedStop: - run.schedStopWorker(pyl.ReplaySchedStop.data) + run.schedStopWorker(pyl.ReplaySchedStop) of SchedPool: - run.schedPoolWorker(pyl.ReplaySchedPool.data) + run.schedPoolWorker(pyl.ReplaySchedPool) # Workers, complex run in background of SchedDaemonBegin: - await run.schedDaemonBegin(pyl.ReplaySchedDaemonBegin.data) + await run.schedDaemonBegin(pyl.ReplaySchedDaemonBegin) of SchedDaemonEnd: - await run.schedDaemonEnd(pyl.ReplaySchedDaemonEnd.data) + await run.schedDaemonEnd(pyl.ReplaySchedDaemonEnd) of SchedPeerBegin: - await run.schedPeerBegin(pyl.ReplaySchedPeerBegin.data) + await run.schedPeerBegin(pyl.ReplaySchedPeerBegin) of SchedPeerEnd: - await run.schedPeerEnd(pyl.ReplaySchedPeerEnd.data) + await run.schedPeerEnd(pyl.ReplaySchedPeerEnd) # Leaf handlers providing input data to background tasks `runDaemon()` # and/or `runPeer()`. of FetchHeaders: - await run.sendHeaders(pyl.ReplayFetchHeaders.data) + await run.sendHeaders(pyl.ReplayFetchHeaders) of SyncHeaders: - await run.sendHeaders(pyl.ReplaySyncHeaders.data) + await run.sendHeaders(pyl.ReplaySyncHeaders) of FetchBodies: - await run.sendBodies(pyl.ReplayFetchBodies.data) + await run.sendBodies(pyl.ReplayFetchBodies) of SyncBodies: - await run.sendBodies(pyl.ReplaySyncBodies.data) + await run.sendBodies(pyl.ReplaySyncBodies) of ImportBlock: - await run.sendBlock(pyl.ReplayImportBlock.data) + await run.sendBlock(pyl.ReplayImportBlock) of SyncBlock: - await run.sendBlock(pyl.ReplaySyncBlock.data) + await run.sendBlock(pyl.ReplaySyncBlock) trace "-dispatch()", n=run.instrNumber, recType=pyl.recType, nBuddies=run.peers.len, nDaemons=(if run.daemon.isNil: 0 else: 1) diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim index fbd141b875..eb94e5f937 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_blocks.nim @@ -58,22 +58,22 @@ proc toStr(e: BeaconError; anyTime = false): string = # ---------------- func getResponse( - instr: TraceFetchBodies; + instr: ReplayFetchBodies; ): Result[FetchBodiesData,BeaconError] = - if instr.fetched.isSome(): - ok(instr.fetched.value) - elif instr.error.isSome(): - err(instr.error.value) + if instr.bag.fetched.isSome(): + ok(instr.bag.fetched.value) + elif instr.bag.error.isSome(): + err(instr.bag.error.value) else: err((ENoException,"","Missing fetch bodies return code",Duration())) func getResponse( - instr: TraceImportBlock; + instr: ReplayImportBlock; ): Result[Duration,BeaconError] = - if instr.elapsed.isSome(): - ok(instr.elapsed.value) - elif instr.error.isSome(): - err(instr.error.value) + if instr.bag.elapsed.isSome(): + ok(instr.bag.elapsed.value) + elif instr.bag.error.isSome(): + err(instr.bag.error.value) else: err((ENoException,"","Missing block import return code",Duration())) @@ -92,23 +92,23 @@ proc fetchBodiesHandler*( const info = "&fetchBodies" let buddy = ReplayBuddyRef(buddy) - var data: TraceFetchBodies + var data: ReplayFetchBodies buddy.withInstr(typeof data, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? - if req != instr.req: + if req != instr.bag.req: raiseAssert info & ": arguments differ" & - ", serial=" & $instr.serial & + ", serial=" & $instr.bag.serial & ", peer=" & $buddy.peer & # ----- ", nBlockHashes=" & $req.blockHashes.len & - ", expected=" & $instr.ivReq.len & + ", expected=" & $instr.bag.ivReq.len & # ----- ", blockHashes=" & req.blockHashes.bnStr(buddy, info) & - ", expected=" & instr.ivReq.bnStr + ", expected=" & instr.bag.ivReq.bnStr data = instr - buddy.withInstr(TraceSyncBodies, info): + buddy.withInstr(ReplaySyncBodies, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? discard # no-op, visual alignment @@ -130,56 +130,56 @@ proc importBlockHandler*( peer = buddy.peerStr peerID = buddy.peerIdStr - var data: TraceImportBlock + var data: ReplayImportBlock buddy.withInstr(typeof data, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? - if effPeerID != instr.effPeerID: + if effPeerID != instr.bag.effPeerID: raiseAssert info & ": eff. peer arguments differ" & ", n=" & $n & - ", serial=" & $instr.serial & + ", serial=" & $instr.bag.serial & ", peer=" & $peer & ", peerID=" & $peerID & ", ethBlock=" & ethBlock.bnStr & # ----- ", effPeerID=" & effPeerID.short & - ", expected=" & instr.effPeerID.short + ", expected=" & instr.bag.effPeerID.short - if ethBlock != instr.ethBlock: + if ethBlock != instr.bag.ethBlock: raiseAssert info & ": block arguments differ" & ", n=" & $n & - ", serial=" & $instr.serial & + ", serial=" & $instr.bag.serial & ", peer=" & $peer & ", peerID=" & $peerID & ", effPeerID=" & effPeerID.short & # ----- ", ethBlock=" & ethBlock.bnStr & - ", expected=%" & instr.ethBlock.bnStr & + ", expected=%" & instr.bag.ethBlock.bnStr & # ----- ", ethBlock=%" & ethBlock.computeRlpHash.short & - ", expected=%" & instr.ethBlock.computeRlpHash.short + ", expected=%" & instr.bag.ethBlock.computeRlpHash.short data = instr let run = buddy.run if not run.fakeImport: let rc = await run.backup.importBlock(buddy, ethBlock, effPeerID) - if rc.isErr or data.error.isSome(): + if rc.isErr or data.bag.error.isSome(): const info = info & ": result values differ" - let serial = data.serial - if rc.isErr and data.error.isNone(): + let serial = data.bag.serial + if rc.isErr and data.bag.error.isNone(): warn info, n, serial, peer, peerID, got="err" & rc.error.toStr, expected="ok" - elif rc.isOk and data.error.isSome(): + elif rc.isOk and data.bag.error.isSome(): warn info, n, serial, peer, peerID, - got="ok", expected="err" & data.error.value.toStr(true) - elif rc.error.excp != data.error.value.excp or - rc.error.msg != data.error.value.msg: + got="ok", expected="err" & data.bag.error.value.toStr(true) + elif rc.error.excp != data.bag.error.value.excp or + rc.error.msg != data.bag.error.value.msg: warn info, n, serial, peer, peerID, got="err" & rc.error.toStr, - expected="err" & data.error.value.toStr(true) + expected="err" & data.bag.error.value.toStr(true) - buddy.withInstr(TraceSyncBlock, info): + buddy.withInstr(ReplaySyncBlock, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? discard # no-op, visual alignment @@ -192,37 +192,37 @@ proc importBlockHandler*( proc sendBodies*( run: ReplayRunnerRef; - instr: TraceFetchBodies|TraceSyncBodies; + instr: ReplayFetchBodies|ReplaySyncBodies; ) {.async: (raises: []).} = ## Stage bodies request/response data const info = instr.replayLabel() let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & - ", serial=" & $instr.serial & - ", peerID=" & instr.peerCtx.value.peerID.short + ", serial=" & $instr.bag.serial & + ", peerID=" & instr.bag.peerCtx.value.peerID.short discard buddy.pushInstr(instr, info) proc sendBlock*( run: ReplayRunnerRef; - instr: TraceImportBlock|TraceSyncBlock; + instr: ReplayImportBlock|ReplaySyncBlock; ) {.async: (raises: []).} = ## Stage block request/response data const info = instr.replayLabel() - if instr.peerCtx.isSome(): + if instr.bag.peerCtx.isSome(): # So it was captured run from a sync peer let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & - ", serial=" & $instr.serial & - ", peerID=" & instr.peerCtx.value.peerID.short + ", serial=" & $instr.bag.serial & + ", peerID=" & instr.bag.peerCtx.value.peerID.short discard buddy.pushInstr(instr, info) # Verify that the daemon is properly initialised elif run.daemon.isNil: raiseAssert info & ": system error (no daemon)" & ", n=" & $run.iNum & - ", serial=" & $instr.serial & + ", serial=" & $instr.bag.serial & ", peer=n/a" else: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim index f8a1279d78..0a974f126c 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_headers.nim @@ -38,12 +38,12 @@ proc `==`(a,b: BlockHeadersRequest): bool = return true func getResponse( - instr: TraceFetchHeaders; + instr: ReplayFetchHeaders; ): Result[FetchHeadersData,BeaconError] = - if instr.fetched.isSome(): - ok(instr.fetched.value) - elif instr.error.isSome(): - err(instr.error.value) + if instr.bag.fetched.isSome(): + ok(instr.bag.fetched.value) + elif instr.bag.error.isSome(): + err(instr.bag.error.value) else: err((ENoException,"","Missing fetch headers return code",Duration())) @@ -63,27 +63,27 @@ proc fetchHeadersHandler*( const info = "&fetchHeaders" let buddy = ReplayBuddyRef(buddy) - var data: TraceFetchHeaders + var data: ReplayFetchHeaders buddy.withInstr(typeof data, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? - if req != instr.req: + if req != instr.bag.req: raiseAssert info & ": arguments differ" & ", n=" & $buddy.iNum & - ", serial=" & $instr.serial & + ", serial=" & $instr.bag.serial & ", peer=" & $buddy.peer & # ----- ", reverse=" & $req.reverse & - ", expected=" & $instr.req.reverse & + ", expected=" & $instr.bag.req.reverse & # ----- ", reqStart=" & req.startBlock.toStr & - ", expected=" & instr.req.startBlock.toStr & + ", expected=" & instr.bag.req.startBlock.toStr & # ----- ", reqLen=" & $req.maxResults & - ", expected=" & $instr.req.maxResults + ", expected=" & $instr.bag.req.maxResults data = instr - buddy.withInstr(TraceSyncHeaders, info): + buddy.withInstr(ReplaySyncHeaders, info): if not instr.isAvailable(): return err(iError.getBeaconError()) # Shutdown? discard # no-op, visual alignment @@ -96,14 +96,14 @@ proc fetchHeadersHandler*( proc sendHeaders*( run: ReplayRunnerRef; - instr: TraceFetchHeaders|TraceSyncHeaders; + instr: ReplayFetchHeaders|ReplaySyncHeaders; ) {.async: (raises: []).} = ## Stage headers request/response data const info = instr.replayLabel() let buddy = run.getPeer(instr, info).valueOr: raiseAssert info & ": getPeer() failed" & ", n=" & $run.iNum & - ", serial=" & $instr.serial + ", serial=" & $instr.bag.serial discard buddy.pushInstr(instr, info) # ------------------------------------------------------------------------------ diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim index 5d25b6ea71..abdf674ec4 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_helpers.nim @@ -34,11 +34,21 @@ type ReplayInstance = ReplayDaemonRef | ReplayBuddyRef - InstrType = TraceSchedDaemonBegin | TraceSchedDaemonEnd | - TraceSchedPeerBegin | TraceSchedPeerEnd | - TraceFetchHeaders | TraceSyncHeaders | - TraceFetchBodies | TraceSyncBodies | - TraceImportBlock | TraceSyncBlock + ReplayMsgInstrType = ReplayFetchHeaders | ReplaySyncHeaders | + ReplayFetchBodies | ReplaySyncBodies | + ReplayImportBlock | ReplaySyncBlock + + ReplayPeerInstrType = ReplaySchedPeerBegin | ReplaySchedPeerEnd | + ReplaySchedStart | ReplaySchedStop | + ReplaySchedPool + + ReplaySchedInstrType = ReplaySchedDaemonBegin | ReplaySchedDaemonEnd | + ReplayPeerInstrType + + ReplayAnyInstrType = ReplayVersionInfo | ReplaySyncActvFailed | + ReplaySyncActivated | ReplaySyncHibernated | + ReplaySchedInstrType | + ReplayMsgInstrType # ------------------------------------------------------------------------------ # Private helper(s) @@ -109,35 +119,35 @@ template waitForConditionImpl( func syncedEnvCondImpl( desc: ReplayInstance; - instr: TraceRecBase; + instr: ReplayAnyInstrType; info: static[string]; ): bool = ## Condition function for `waitForConditionImpl()` for synchronising state. ## let ctx = desc.run.ctx - if instr.hdrUnpr.isSome(): - if instr.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: + if instr.bag.hdrUnpr.isSome(): + if instr.bag.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: return false - if 0 < instr.hdrUnpr.value.hChunks: - if instr.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): + if 0 < instr.bag.hdrUnpr.value.hChunks: + if instr.bag.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): return false let iv = ctx.hdr.unprocessed.le().expect "valid iv" - if instr.hdrUnpr.value.hLast != iv.maxPt or - instr.hdrUnpr.value.hLastLen != iv.len: + if instr.bag.hdrUnpr.value.hLast != iv.maxPt or + instr.bag.hdrUnpr.value.hLastLen != iv.len: return false - if instr.antecedent != ctx.hdrCache.antecedent.number: + if instr.bag.antecedent != ctx.hdrCache.antecedent.number: return false - if instr.blkUnpr.isSome(): - if instr.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: + if instr.bag.blkUnpr.isSome(): + if instr.bag.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: return false - if 0 < instr.blkUnpr.value.bChunks: - if instr.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): + if 0 < instr.bag.blkUnpr.value.bChunks: + if instr.bag.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): return false let iv = ctx.blk.unprocessed.ge().expect "valid iv" - if instr.blkUnpr.value.bLeast != iv.minPt or - instr.blkUnpr.value.bLeastLen != iv.len: + if instr.bag.blkUnpr.value.bLeast != iv.minPt or + instr.bag.blkUnpr.value.bLeastLen != iv.len: return false return true @@ -145,18 +155,18 @@ func syncedEnvCondImpl( proc newPeerImpl( run: ReplayRunnerRef; - instr: TraceSchedStart|TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; + instr: ReplayPeerInstrType; info: static[string]; ): Opt[ReplayBuddyRef] = ## Register a new peer. ## - if instr.peerCtx.isNone(): - warn info & ": missing peer ctx", n=run.instrNumber, serial=instr.serial + if instr.bag.peerCtx.isNone(): + warn info & ": missing peer ctx", n=run.instrNumber, serial=instr.bag.serial return err() - run.peers.withValue(instr.peerCtx.value.peerID, val): + run.peers.withValue(instr.bag.peerCtx.value.peerID, val): warn info & ": peer exists already", n=run.instrNumber, - serial=instr.serial, peer=($val.peer) + serial=instr.bag.serial, peer=($val.peer) val.isNew = false return ok(val[]) @@ -165,20 +175,20 @@ proc newPeerImpl( run: run, ctx: run.ctx, only: BeaconBuddyData( - nRespErrors: (instr.peerCtx.value.nHdrErrors, - instr.peerCtx.value.nBlkErrors)), - peerID: instr.peerCtx.value.peerID, + nRespErrors: (instr.bag.peerCtx.value.nHdrErrors, + instr.bag.peerCtx.value.nBlkErrors)), + peerID: instr.bag.peerCtx.value.peerID, peer: Peer( dispatcher: run.ethState.capa, peerStates: run.ethState.prots, remote: Node( node: ENode( address: enode.Address( - ip: instr.peerIP, - tcpPort: instr.peerPort, - udpPort: instr.peerPort))))) + ip: instr.bag.peerIP, + tcpPort: instr.bag.peerPort, + udpPort: instr.bag.peerPort))))) - run.peers[instr.peerCtx.value.peerID] = buddy + run.peers[instr.bag.peerCtx.value.peerID] = buddy return ok(move buddy) # ------------------------------------------------------------------------------ @@ -186,8 +196,8 @@ proc newPeerImpl( # ------------------------------------------------------------------------------ proc baseStatesDifferImpl( - desc: ReplayRunnerRef | ReplayInstance; - instr: TraceRecBase; + desc: ReplayRunnerRef|ReplayInstance; + instr: ReplayAnyInstrType; ignLatestNum: static[bool]; info: static[string]; ): bool = @@ -201,7 +211,7 @@ proc baseStatesDifferImpl( let ctx = run.ctx n = run.instrNumber - serial = instr.serial + serial = instr.bag.serial var statesDiffer = false @@ -209,48 +219,49 @@ proc baseStatesDifferImpl( statesDiffer = true info info & ": serial numbers differ", n, peer, serial, expected=n - if ctx.chain.baseNumber != instr.baseNum: + if ctx.chain.baseNumber != instr.bag.baseNum: statesDiffer = true info info & ": base blocks differ", n, serial, peer, - base=instr.baseNum.bnStr, expected=ctx.chain.baseNumber.bnStr + base=instr.bag.baseNum.bnStr, expected=ctx.chain.baseNumber.bnStr when not ignLatestNum: - if ctx.chain.latestNumber != instr.latestNum: + if ctx.chain.latestNumber != instr.bag.latestNum: statesDiffer = true info info & ": latest blocks differ", n, serial, peer, - latest=instr.latestNum.bnStr, expected=ctx.chain.latestNumber.bnStr + latest=instr.bag.latestNum.bnStr, expected=ctx.chain.latestNumber.bnStr - if ctx.pool.lastState != instr.syncState: + if ctx.pool.lastState != instr.bag.syncState: statesDiffer = true info info & ": sync states differ", n, serial, peer, - state=ctx.pool.lastState, expected=instr.syncState + state=ctx.pool.lastState, expected=instr.bag.syncState - if ctx.hdrCache.state != instr.chainMode: + if ctx.hdrCache.state != instr.bag.chainMode: statesDiffer = true info info & ": header chain modes differ", n, serial, peer, - chainMode=ctx.hdrCache.state, expected=instr.chainMode - elif instr.chainMode in {collecting,ready,orphan} and - instr.antecedent != ctx.hdrCache.antecedent.number: + chainMode=ctx.hdrCache.state, expected=instr.bag.chainMode + elif instr.bag.chainMode in {collecting,ready,orphan} and + instr.bag.antecedent != ctx.hdrCache.antecedent.number: statesDiffer = true info info & ": header chain antecedents differ", n, serial, peer, - antecedent=ctx.hdrCache.antecedent.bnStr, expected=instr.antecedent.bnStr + antecedent=ctx.hdrCache.antecedent.bnStr, + expected=instr.bag.antecedent.bnStr - if ctx.pool.nBuddies != instr.nPeers.int: + if ctx.pool.nBuddies != instr.bag.nPeers.int: statesDiffer = true info info & ": number of active peers differs", n, serial, peer, - nBuddies=ctx.pool.nBuddies, expected=instr.nPeers + nBuddies=ctx.pool.nBuddies, expected=instr.bag.nPeers - if ctx.poolMode != instr.poolMode: + if ctx.poolMode != instr.bag.poolMode: statesDiffer = true info info & ": pool modes/reorgs differ", n, serial, peer, - poolMode=ctx.poolMode, expected=instr.poolMode + poolMode=ctx.poolMode, expected=instr.bag.poolMode return statesDiffer proc unprocListsDifferImpl( - desc: ReplayRunnerRef | ReplayInstance; - instr: TraceRecBase; + desc: ReplayRunnerRef|ReplayInstance; + instr: ReplayAnyInstrType; info: static[string]; ): bool = when desc is ReplayRunnerRef: @@ -263,91 +274,91 @@ proc unprocListsDifferImpl( let ctx = run.ctx n = run.instrNumber - serial = instr.serial + serial = instr.bag.serial var statesDiffer = false # Unprocessed block numbers for header - if instr.hdrUnpr.isSome(): - if instr.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: + if instr.bag.hdrUnpr.isSome(): + if instr.bag.hdrUnpr.value.hChunks != ctx.hdr.unprocessed.chunks().uint: statesDiffer = true info info & ": unproc headers lists differ", n, serial, peer, listChunks=ctx.hdr.unprocessed.chunks(), - expected=instr.hdrUnpr.value.hChunks - if 0 < instr.hdrUnpr.value.hChunks: - if instr.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): + expected=instr.bag.hdrUnpr.value.hChunks + if 0 < instr.bag.hdrUnpr.value.hChunks: + if instr.bag.hdrUnpr.value.hLen != ctx.hdr.unprocessed.total(): statesDiffer = true info info & ": unproc headers lists differ", n, serial, peer, listLen=ctx.hdr.unprocessed.total(), - expected=instr.hdrUnpr.value.hLen + expected=instr.bag.hdrUnpr.value.hLen let iv = ctx.hdr.unprocessed.le().expect "valid iv" - if instr.hdrUnpr.value.hLastLen != iv.len: + if instr.bag.hdrUnpr.value.hLastLen != iv.len: statesDiffer = true info info & ": unproc headers lists differ", n, serial, peer, - lastIvLen=iv.len, expected=instr.hdrUnpr.value.hLastLen - if instr.hdrUnpr.value.hLast != iv.maxPt: + lastIvLen=iv.len, expected=instr.bag.hdrUnpr.value.hLastLen + if instr.bag.hdrUnpr.value.hLast != iv.maxPt: statesDiffer = true info info & ": unproc headers lists differ", n, serial, peer, - lastIvMax=iv.maxPt, expected=instr.hdrUnpr.value.hLast + lastIvMax=iv.maxPt, expected=instr.bag.hdrUnpr.value.hLast # Unprocessed block numbers for blocks - if instr.blkUnpr.isSome(): - if instr.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: + if instr.bag.blkUnpr.isSome(): + if instr.bag.blkUnpr.value.bChunks != ctx.blk.unprocessed.chunks().uint: statesDiffer = true info info & ": unproc blocks lists differ", n, serial, peer, listChunks=ctx.blk.unprocessed.chunks(), - expected=instr.blkUnpr.value.bChunks - if 0 < instr.blkUnpr.value.bChunks: - if instr.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): + expected=instr.bag.blkUnpr.value.bChunks + if 0 < instr.bag.blkUnpr.value.bChunks: + if instr.bag.blkUnpr.value.bLen != ctx.blk.unprocessed.total(): statesDiffer = true info info & ": unproc blocks lists differ", n, serial, peer, listLen=ctx.blk.unprocessed.total(), - expected=instr.blkUnpr.value.bLen + expected=instr.bag.blkUnpr.value.bLen let iv = ctx.blk.unprocessed.ge().expect "valid iv" - if instr.blkUnpr.value.bLeastLen != iv.len: + if instr.bag.blkUnpr.value.bLeastLen != iv.len: statesDiffer = true info info & ": unproc blocks lists differ", n, serial, peer, - lastIvLen=iv.len, expected=instr.blkUnpr.value.bLeastLen - if instr.blkUnpr.value.bLeast != iv.minPt: + lastIvLen=iv.len, expected=instr.bag.blkUnpr.value.bLeastLen + if instr.bag.blkUnpr.value.bLeast != iv.minPt: statesDiffer = true info info & ": unproc blocks lists differ", n, serial, peer, - lastIvMax=iv.maxPt, expected=instr.blkUnpr.value.bLeast + lastIvMax=iv.maxPt, expected=instr.bag.blkUnpr.value.bLeast return statesDiffer proc peerStatesDifferImpl( buddy: ReplayBuddyRef; - instr: TraceRecBase; + instr: ReplayAnyInstrType; info: static[string]; ): bool = let peer = buddy.peer n = buddy.run.instrNumber - serial = instr.serial + serial = instr.bag.serial var statesDiffer = false - if instr.peerCtx.isNone(): + if instr.bag.peerCtx.isNone(): statesDiffer = true info info & ": peer ctx values differ", n, serial, peer, ctx="n/a" else: - if instr.peerCtx.value.peerCtrl != buddy.ctrl.state: + if instr.bag.peerCtx.value.peerCtrl != buddy.ctrl.state: statesDiffer = true info info & ": peer ctrl states differ", n, serial, peer, - ctrl=buddy.ctrl.state, expected=instr.peerCtx.value.peerCtrl + ctrl=buddy.ctrl.state, expected=instr.bag.peerCtx.value.peerCtrl - if instr.peerCtx.value.nHdrErrors != buddy.only.nRespErrors.hdr: + if instr.bag.peerCtx.value.nHdrErrors != buddy.only.nRespErrors.hdr: statesDiffer = true info info & ": peer header errors differ", n, serial, peer, nHdrErrors=buddy.only.nRespErrors.hdr, - expected=instr.peerCtx.value.nHdrErrors + expected=instr.bag.peerCtx.value.nHdrErrors - if instr.peerCtx.value.nBlkErrors != buddy.only.nRespErrors.blk: + if instr.bag.peerCtx.value.nBlkErrors != buddy.only.nRespErrors.blk: statesDiffer = true info info & ": peer body errors differ", n, serial, peer, nBlkErrors=buddy.only.nRespErrors.blk, - expected=instr.peerCtx.value.nBlkErrors + expected=instr.bag.peerCtx.value.nBlkErrors return statesDiffer @@ -376,9 +387,9 @@ func peerIdStr*(desc: ReplayInstance): string = elif desc is ReplayDaemonRef: "n/a" -func frameIdStr*(instr: InstrType|TraceSchedStop|TraceSchedPool): string = - if instr.frameID.isSome(): - instr.frameID.value.idStr +func frameIdStr*(instr: ReplaySchedInstrType): string = + if instr.bag.frameID.isSome(): + instr.bag.frameID.value.idStr else: "n/a" @@ -401,8 +412,8 @@ proc stopOk*(run: ReplayRunnerRef; info: static[string]) = # ----------------- proc checkSyncerState*( - desc: ReplayRunnerRef | ReplayInstance; - instr: TraceRecBase; + desc: ReplayRunnerRef|ReplayInstance; + instr: ReplayAnyInstrType; ignLatestNum: static[bool]; info: static[string]; ): bool @@ -424,8 +435,8 @@ proc checkSyncerState*( return statesDiffer proc checkSyncerState*( - desc: ReplayRunnerRef | ReplayInstance; - instr: TraceRecBase; + desc: ReplayRunnerRef|ReplayInstance; + instr: ReplayAnyInstrType; info: static[string]; ): bool {.discardable.} = @@ -437,23 +448,23 @@ proc checkSyncerState*( proc getPeer*( run: ReplayRunnerRef; - instr: TraceRecBase; + instr: ReplayPeerInstrType|ReplayMsgInstrType; info: static[string]; ): Opt[ReplayBuddyRef] = ## Get peer from peers table (if any) - if instr.peerCtx.isNone(): - warn info & ": missing peer ctx", n=run.iNum, serial=instr.serial + if instr.bag.peerCtx.isNone(): + warn info & ": missing peer ctx", n=run.iNum, serial=instr.bag.serial else: - run.peers.withValue(instr.peerCtx.value.peerID, buddy): + run.peers.withValue(instr.bag.peerCtx.value.peerID, buddy): return ok(buddy[]) - debug info & ": no peer", n=run.iNum, serial=instr.serial, - peerID=instr.peerCtx.value.peerID.short + debug info & ": no peer", n=run.iNum, serial=instr.bag.serial, + peerID=instr.bag.peerCtx.value.peerID.short return err() proc newPeer*( run: ReplayRunnerRef; - instr: TraceSchedStart; + instr: ReplaySchedStart; info: static[string]; ): Opt[ReplayBuddyRef] = ## Register a new peer. @@ -463,16 +474,16 @@ proc newPeer*( proc getOrNewPeerFrame*( run: ReplayRunnerRef; - instr: TraceSchedStop|TraceSchedPool|TraceSchedPeerBegin; + instr: ReplayPeerInstrType; info: static[string]; ): Opt[ReplayBuddyRef] = ## Get an existing one or register a new peer and set up `stage[0]`. ## - if instr.peerCtx.isNone(): + if instr.bag.peerCtx.isNone(): return err() var buddy: ReplayBuddyRef - run.peers.withValue(instr.peerCtx.value.peerID, val): + run.peers.withValue(instr.bag.peerCtx.value.peerID, val): buddy = val[] buddy.isNew = false do: @@ -480,12 +491,12 @@ proc getOrNewPeerFrame*( if buddy.frameID.isSome(): warn info & ": peer frameID unexpected", n=buddy.iNum, - serial=instr.serial, frameID=buddy.frameIdStr, expected="n/a" - if instr.frameID.isNone(): + serial=instr.bag.serial, frameID=buddy.frameIdStr, expected="n/a" + if instr.bag.frameID.isNone(): warn info & ": peer instr frameID missing", n=buddy.iNum, - serial=instr.serial, frameID="n/a" + serial=instr.bag.serial, frameID="n/a" - buddy.frameID = instr.frameID + buddy.frameID = instr.bag.frameID return ok(move buddy) @@ -519,21 +530,21 @@ proc getDaemon*( proc newDaemonFrame*( run: ReplayRunnerRef; - instr: TraceSchedDaemonBegin; + instr: ReplaySchedDaemonBegin; info: static[string]; ): Opt[ReplayDaemonRef] = ## Similar to `getOrNewPeerFrame()` for daemon. if run.daemon.isNil: - if instr.frameID.isNone(): + if instr.bag.frameID.isNone(): warn info & ": daemon instr frameID missing", n=run.iNum, - serial=instr.serial, frameID="n/a" + serial=instr.bag.serial, frameID="n/a" run.daemon = ReplayDaemonRef( run: run, - frameID: instr.frameID) + frameID: instr.bag.frameID) return ok(run.daemon) - warn info & ": daemon already registered", n=run.iNum, serial=instr.serial, - frameID=instr.frameIdStr + warn info & ": daemon already registered", n=run.iNum, + serial=instr.bag.serial, frameID=instr.frameIdStr return err() @@ -554,7 +565,7 @@ proc delDaemon*( proc waitForSyncedEnv*( desc: ReplayInstance; - instr: InstrType; + instr: ReplaySchedInstrType ; info: static[string]; ): Future[ReplayWaitResult] {.async: (raises: []).} = @@ -564,14 +575,14 @@ proc waitForSyncedEnv*( # The scheduler (see `sync_sched.nim`) might have disconnected the peer # already as is captured in the instruction environment. This does not # apply to `zombie` settings which will be done by the application. - if instr.peerCtx.isNone(): - warn info & ": missing peer ctx", n=desc.iNum, serial=instr.serial + if instr.bag.peerCtx.isNone(): + warn info & ": missing peer ctx", n=desc.iNum, serial=instr.bag.serial return err((ENoException,"",info&": missing peer ctx")) - if instr.peerCtx.value.peerCtrl == Stopped and not desc.ctrl.stopped: + if instr.bag.peerCtx.value.peerCtrl == Stopped and not desc.ctrl.stopped: desc.ctrl.stopped = true let - serial {.inject,used.} = instr.serial + serial {.inject,used.} = instr.bag.serial peer {.inject,used.} = desc.peerStr peerID {.inject,used.} = desc.peerIdStr @@ -598,44 +609,39 @@ proc waitForSyncedEnv*( proc processFinishedClearFrame*( desc: ReplayInstance; - instr: TraceSchedDaemonBegin|TraceSchedPeerBegin|TraceSchedPool; + instr: ReplaySchedDaemonBegin|ReplaySchedPeerBegin|ReplaySchedPool; info: static[string]; ) = ## Register that the process has finished ## # Verify that sub-processes did not change the environment - if desc.frameID != instr.frameID: - warn info & ": frameIDs differ", n=desc.iNum, serial=instr.serial, + if desc.frameID != instr.bag.frameID: + warn info & ": frameIDs differ", n=desc.iNum, serial=instr.bag.serial, peer=desc.peerStr, frameID=desc.frameIdStr, expected=instr.frameIdStr # Mark the pocess `done` desc.frameID = Opt.none(uint) trace info & ": terminating", n=desc.iNum, - serial=instr.serial, frameID=instr.frameIdStr, peer=desc.peerStr + serial=instr.bag.serial, frameID=instr.frameIdStr, peer=desc.peerStr template whenProcessFinished*( desc: ReplayInstance; - instr: TraceSchedDaemonEnd|TraceSchedPeerEnd; + instr: ReplaySchedDaemonEnd|ReplaySchedPeerEnd; info: static[string]; ): ReplayWaitResult = ## Async/template ## - ## Execude the argument `code` when the process related to the `instr` - ## argument flag has finished. The variables and functions available for - ## `code` are: - ## * `error` -- error data, initialised if `instr.isAvailable()` is `false` - ## var bodyRc = ReplayWaitResult.ok() block body: let peer {.inject,used.} = desc.peerStr peerID {.inject,used.} = desc.peerIdStr - serial {.inject,used.} = instr.serial + serial {.inject,used.} = instr.bag.serial if desc.frameID.isSome(): - doAssert desc.frameID == instr.frameID + doAssert desc.frameID == instr.bag.frameID trace info & ": wait for terminated", n=desc.iNum, serial, frameID=instr.frameIdStr, peer, peerID @@ -672,7 +678,7 @@ template whenProcessFinished*( template pushInstr*( desc: ReplayInstance; - instr: untyped; + instr: ReplayMsgInstrType; info: static[string]; ): ReplayWaitResult = ## Async/template @@ -682,24 +688,20 @@ template pushInstr*( ## var bodyRc = ReplayWaitResult.ok() block: - const dataType {.inject.} = (typeof instr).toTraceRecType - type M = (typeof instr).toReplayMsgType - # Verify that the stage is based on a proper environment - doAssert desc.frameID.isSome() # this is not `instr.frameID` + doAssert desc.frameID.isSome() # this is not `instr.bag.frameID` let peer {.inject,used.} = desc.peerStr peerID {.inject,used.} = desc.peerIdStr - serial {.inject.} = instr.serial + dataType {.inject.} = instr.recType + serial {.inject.} = instr.bag.serial doAssert serial == desc.iNum doAssert desc.message.isNil # Stage/push session data - desc.message = M( - recType: dataType, - instr: instr) + desc.message = instr block body: # Wait for sync # FIXME, really needed? @@ -734,7 +736,7 @@ template pushInstr*( template withInstr*( desc: ReplayInstance; - I: type; # `instr` type + R: type ReplayMsgInstrType; info: static[string]; code: untyped; ) = @@ -742,16 +744,15 @@ template withInstr*( ## ## Execude the argument `code` with the data sent by a feeder. The variables ## and functions available for `code` are: - ## * `instr` -- instruction data, available if `instr.isAvailable()` is `true` + ## * `instr` -- instr data, available if `instr.isAvailable()` is `true` ## * `iError` -- error data, initialised if `instr.isAvailable()` is `false` ## block: - const dataType {.inject.} = I.toTraceRecType - type M = I.toReplayMsgType + const dataType {.inject.} = (typeof R().bag).toTraceRecType - when I is TraceFetchBodies or - I is TraceSyncBodies or - I is TraceImportBlock: + when R is ReplayFetchBodies or + R is ReplaySyncBodies or + R is ReplayImportBlock: const ignLatestNum = true # relax, de-noise else: const ignLatestNum = false @@ -761,54 +762,54 @@ template withInstr*( peer {.inject,used.} = desc.peerStr peerID {.inject,used.} = desc.peerIdStr - trace info & ": get data", n=desc.iNum, serial="n/a", - frameID="n/a", peer, dataType + trace info & ": get data", n=desc.iNum, serial="n/a", peer, dataType # Reset flag and wait for staged data to disappear from stack let rc = run.waitForConditionImpl(info): if tmoPending: - debug info & ": expecting data", n, serial="n/a", frameID="n/a", + debug info & ": expecting data", n, serial="n/a", peer, peerID, dataType, count not desc.message.isNil # cond result var iError {.inject.}: ReplayWaitError - instr {.inject.}: I + instr {.inject.}: R if rc.isOk(): - instr = M(desc.message).instr + instr = R(desc.message) doAssert desc.message.recType == dataType - doAssert instr.serial == desc.iNum + doAssert instr.bag.serial == desc.iNum when desc is ReplayBuddyRef: # The scheduler (see `sync_sched.nim`) might have disconnected the # peer already which would be captured in the instruction environment. # This does not apply to `zombie` settings which will be handled by # the application `code`. - if instr.peerCtx.isNone(): - warn info & ": missing peer ctx", n=desc.iNum, serial=instr.serial, - frameID=instr.frameIdStr, peer, peerID, dataType + if instr.bag.peerCtx.isNone(): + warn info & ": missing peer ctx", n=desc.iNum, + serial=instr.bag.serial, peer, peerID, dataType desc.ctrl.stopped = true - elif instr.peerCtx.value.peerCtrl == Stopped and not desc.ctrl.stopped: + elif instr.bag.peerCtx.value.peerCtrl == Stopped and + not desc.ctrl.stopped: desc.ctrl.stopped = true else: iError = rc.error - template isAvailable(_: typeof instr): bool {.used.} = rc.isOk() + template isAvailable(_: R): bool {.used.} = rc.isOk() code if rc.isOk(): doAssert not desc.message.isNil doAssert desc.message.recType == dataType - doAssert instr.serial == desc.iNum + doAssert instr.bag.serial == desc.iNum desc.checkSyncerState(instr, ignLatestNum, info) - debug info & ": got data", n=desc.iNum, serial=instr.serial, - frameID=instr.frameIdStr, peer, peerID, dataType + debug info & ": got data", n=desc.iNum, serial=instr.bag.serial, + peer, peerID, dataType - desc.message = M(nil) + desc.message = ReplayPayloadRef(nil) discard # no-op, visual alignment diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim index b75976a851..46a0ba50ce 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sched.nim @@ -27,35 +27,35 @@ logScope: proc schedDaemonProcessImpl( daemon: ReplayDaemonRef; - instr: TraceSchedDaemonBegin; + instr: ReplaySchedDaemonBegin; info: static[string]; ) {.async: (raises: []).} = ## Run the task `schedDaemon()`. This function has to be run background ## process (using `asyncSpawn`.) ## let run = daemon.run - trace info & ": begin", n=run.iNum, serial=instr.serial, - frameID=instr.frameIdStr, syncState=instr.syncState + trace info & ": begin", n=run.iNum, serial=instr.bag.serial, + frameID=instr.frameIdStr, syncState=instr.bag.syncState discard await run.backup.schedDaemon(run.ctx) daemon.processFinishedClearFrame(instr, info) - trace info & ": end", n=run.iNum, serial=instr.serial, - frameID=instr.frameIdStr, syncState=instr.syncState + trace info & ": end", n=run.iNum, serial=instr.bag.serial, + frameID=instr.frameIdStr, syncState=instr.bag.syncState proc schedPeerProcessImpl( buddy: ReplayBuddyRef; - instr: TraceSchedPeerBegin; + instr: ReplaySchedPeerBegin; info: static[string]; ) {.async: (raises: []).} = ## Run the task `schedPeer()`. This function has to be run background ## process (using `asyncSpawn`.) ## let run = buddy.run - trace info & ": begin", n=run.iNum, serial=instr.serial, + trace info & ": begin", n=run.iNum, serial=instr.bag.serial, frameID=instr.frameIdStr, peer=($buddy.peer), peerID=buddy.peerID.short, - syncState=instr.syncState + syncState=instr.bag.syncState # Activate peer buddy.run.nPeers.inc @@ -63,9 +63,9 @@ proc schedPeerProcessImpl( discard await run.backup.schedPeer(buddy) buddy.processFinishedClearFrame(instr, info) - trace info & ": end", n=run.iNum, serial=instr.serial, + trace info & ": end", n=run.iNum, serial=instr.bag.serial, frameID=instr.frameIdStr, peer=($buddy.peer), peerID=buddy.peerID.short, - syncState=instr.syncState + syncState=instr.bag.syncState # ------------------------------------------------------------------------------ # Public dispatcher handlers @@ -73,7 +73,7 @@ proc schedPeerProcessImpl( proc schedDaemonBegin*( run: ReplayRunnerRef; - instr: TraceSchedDaemonBegin; + instr: ReplaySchedDaemonBegin; ) {.async: (raises: []).} = ## Run the `schedDaemon()` task. ## @@ -86,7 +86,7 @@ proc schedDaemonBegin*( proc schedDaemonEnd*( run: ReplayRunnerRef; - instr: TraceSchedDaemonEnd; + instr: ReplaySchedDaemonEnd; ) {.async: (raises: []).} = ## Clean up (in foreground) after `schedDaemon()` process has terminated. ## @@ -98,7 +98,7 @@ proc schedDaemonEnd*( proc schedStartWorker*( run: ReplayRunnerRef; - instr: TraceSchedStart; + instr: ReplaySchedStart; ) = ## Runs `schedStart()` in the foreground. ## @@ -108,12 +108,13 @@ proc schedStartWorker*( buddy = run.newPeer(instr, info).valueOr: return accept = run.backup.schedStart(buddy) - trace info & ": begin", n=run.iNum, serial=instr.serial, + trace info & ": begin", n=run.iNum, serial=instr.bag.serial, peer=($buddy.peer), peerID=buddy.peerID.short - if accept != instr.accept: - warn info & ": result argument differs", n=run.iNum, serial=instr.serial, - peer=buddy.peer, expected=instr.accept, result=accept + if accept != instr.bag.accept: + warn info & ": result argument differs", n=run.iNum, + serial=instr.bag.serial, peer=buddy.peer, expected=instr.bag.accept, + result=accept # Syncer state was captured when leaving the `schedStart()` handler. buddy.checkSyncerState(instr, ignLatestNum=true, info) # relaxed check @@ -121,13 +122,13 @@ proc schedStartWorker*( if not accept: buddy.delPeer(info) # Clean up - trace info & ": end", n=run.iNum, serial=instr.serial, + trace info & ": end", n=run.iNum, serial=instr.bag.serial, peer=($buddy.peer), peerID=buddy.peerID.short proc schedStopWorker*( run: ReplayRunnerRef; - instr: TraceSchedStop; + instr: ReplaySchedStop; ) = ## Runs `schedStop()` in the foreground. ## @@ -141,23 +142,23 @@ proc schedStopWorker*( # which has its desciptor sort of unintialised (relative to `instr`.) if not buddy.isNew: # Syncer state was captured when leaving the `schedStop()` handler. - if instr.peerCtx.isNone(): - warn info & ": peer ctx missing", n=run.iNum, serial=instr.serial + if instr.bag.peerCtx.isNone(): + warn info & ": peer ctx missing", n=run.iNum, serial=instr.bag.serial return - if instr.peerCtx.value.peerCtrl == Stopped and not buddy.ctrl.stopped: + if instr.bag.peerCtx.value.peerCtrl == Stopped and not buddy.ctrl.stopped: buddy.ctrl.stopped = true buddy.checkSyncerState(instr, info) # Clean up buddy.delPeer(info) - trace info & ": done", n=run.iNum, serial=instr.serial, + trace info & ": done", n=run.iNum, serial=instr.bag.serial, peer=($buddy.peer), peerID=buddy.peerID.short proc schedPoolWorker*( run: ReplayRunnerRef; - instr: TraceSchedPool; + instr: ReplaySchedPool; ) = ## Runs `schedPool()` in the foreground. ## @@ -165,26 +166,26 @@ proc schedPoolWorker*( let buddy = run.getOrNewPeerFrame(instr, info).valueOr: return if 0 < run.nPeers: - warn info & ": no active peers allowed", n=run.iNum, serial=instr.serial, - peer=buddy.peer, nPeers=run.nPeers, expected=0 + warn info & ": no active peers allowed", n=run.iNum, + serial=instr.bag.serial, peer=buddy.peer, nPeers=run.nPeers, expected=0 # The scheduler will reset the `poolMode` flag before starting the # `schedPool()` function. run.ctx.poolMode = false - discard run.backup.schedPool(buddy, instr.last, instr.laps.int) + discard run.backup.schedPool(buddy, instr.bag.last, instr.bag.laps.int) # Syncer state was captured when leaving the `schedPool()` handler. buddy.checkSyncerState(instr, info) buddy.processFinishedClearFrame(instr, info) - info info & ": done", n=run.iNum, serial=instr.serial, + info info & ": done", n=run.iNum, serial=instr.bag.serial, peer=($buddy.peer), peerID=buddy.peerID.short proc schedPeerBegin*( run: ReplayRunnerRef; - instr: TraceSchedPeerBegin; + instr: ReplaySchedPeerBegin; ) {.async: (raises: []).} = ## Run the `schedPeer()` task. ## @@ -197,7 +198,7 @@ proc schedPeerBegin*( proc schedPeerEnd*( run: ReplayRunnerRef; - instr: TraceSchedPeerEnd; + instr: ReplaySchedPeerEnd; ) {.async: (raises: []).} = ## Clean up (in foreground) after `schedPeer()` process has terminated. ## diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim index 9db8ef2e7b..5bf9e709ee 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_sync.nim @@ -25,22 +25,16 @@ logScope: # Public dispatcher handlers # ------------------------------------------------------------------------------ -proc syncActvFailedWorker*( - run: ReplayRunnerRef; - instr: TraceSyncActvFailed; - ) = +proc syncActvFailedWorker*(run: ReplayRunnerRef; instr: ReplaySyncActvFailed) = const info = instr.replayLabel() - trace info, n=run.iNum, serial=instr.serial + trace info, n=run.iNum, serial=instr.bag.serial -proc syncActivateWorker*( - run: ReplayRunnerRef; - instr: TraceSyncActivated; - ) = +proc syncActivateWorker*(run: ReplayRunnerRef; instr: ReplaySyncActivated) = const info = instr.replayLabel() let - serial = instr.serial + serial = instr.bag.serial ctx = run.ctx if not ctx.hibernate: @@ -48,16 +42,16 @@ proc syncActivateWorker*( return var activationOK = true - if ctx.chain.baseNumber != instr.baseNum: + if ctx.chain.baseNumber != instr.bag.baseNum: error info & ": cannot activate (bases must match)", n=run.iNum, serial, - base=ctx.chain.baseNumber.bnStr, expected=instr.baseNum.bnStr + base=ctx.chain.baseNumber.bnStr, expected=instr.bag.baseNum.bnStr activationOK = false if activationOK: - ctx.hdrCache.headTargetUpdate(instr.head, instr.finHash) + ctx.hdrCache.headTargetUpdate(instr.bag.head, instr.bag.finHash) # Set the number of active buddies (avoids some moaning.) - run.ctx.pool.nBuddies = instr.nPeers.int + run.ctx.pool.nBuddies = instr.bag.nPeers.int run.checkSyncerState(instr, info) if ctx.hibernate or not activationOK: @@ -70,17 +64,14 @@ proc syncActivateWorker*( debug info, n=run.iNum, serial -proc syncSuspendWorker*( - run: ReplayRunnerRef; - instr: TraceSyncHibernated; - ) = +proc syncSuspendWorker*(run: ReplayRunnerRef; instr: ReplaySyncHibernated) = const info = instr.replayLabel() if not run.ctx.hibernate: run.stopError(info & ": suspend failed") return run.checkSyncerState(instr, info) - debug info, n=run.iNum, serial=instr.serial + debug info, n=run.iNum, serial=instr.bag.serial # Shutdown if there are no remaining sessions left if 1 < run.nSessions: diff --git a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim index 08e194bccb..73247f1a00 100644 --- a/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim +++ b/tools/syncer/replay/replay_runner/runner_dispatch/dispatch_version.nim @@ -25,14 +25,11 @@ logScope: # Public dispatcher handlers # ------------------------------------------------------------------------------ -proc versionInfoWorker*( - run: ReplayRunnerRef; - instr: TraceVersionInfo; - ) = +proc versionInfoWorker*(run: ReplayRunnerRef; instr: ReplayVersionInfo) = const info = instr.replayLabel() let - serial = instr.serial + serial = instr.bag.serial ctx = run.ctx var versionOK = true @@ -41,23 +38,23 @@ proc versionInfoWorker*( error info & ": not the first record", serial, expected=1 versionOK = false - if run.instrNumber != 1: - error info & ": record count mismatch", n=run.instrNumber, expected=1 + if run.iNum != 1: + error info & ": record count mismatch", n=run.iNum, expected=1 versionOK = false - if instr.version != TraceVersionID: + if instr.bag.version != TraceVersionID: error info & ": wrong version", serial, - traceLayoutVersion=instr.version, expected=TraceVersionID + traceLayoutVersion=instr.bag.version, expected=TraceVersionID versionOK = false - if instr.networkId != ctx.chain.com.networkId: + if instr.bag.networkId != ctx.chain.com.networkId: error info & ": wrong network", serial, - networkId=instr.networkId, expected=ctx.chain.com.networkId + networkId=instr.bag.networkId, expected=ctx.chain.com.networkId versionOK = false - if ctx.chain.baseNumber < instr.baseNum: + if ctx.chain.baseNumber < instr.bag.baseNum: error info & ": cannot start (base too low)", serial, - base=ctx.chain.baseNumber.bnStr, replayBase=instr.baseNum.bnStr + base=ctx.chain.baseNumber.bnStr, replayBase=instr.bag.baseNum.bnStr versionOK = false if not ctx.hibernate: From c3715633b9c8b06b19caa4a8034eccb5ec21fbb2 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Mon, 29 Sep 2025 19:25:43 +0100 Subject: [PATCH 17/18] Adjust to changed main API --- tools/syncer/syncer_test_client_replay.nim | 9 ++++----- tools/syncer/syncer_test_client_trace.nim | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tools/syncer/syncer_test_client_replay.nim b/tools/syncer/syncer_test_client_replay.nim index 28e807d536..5fef45288c 100644 --- a/tools/syncer/syncer_test_client_replay.nim +++ b/tools/syncer/syncer_test_client_replay.nim @@ -95,13 +95,12 @@ let leftConf = makeConfig(cmdLine = leftOpts) - nodeConf = leftConf.setupExeClientNode() - -# Update node config for lazy beacon sync update -nodeConf.beaconSyncRef = BeaconSyncRef.init rightConf.beaconSyncConfig + # Update node config for lazy beacon sync update + nodeConf = NimbusNode( + beaconSyncRef: BeaconSyncRef.init rightConf.beaconSyncConfig) # Run execution client -nodeConf.runExeClient(leftConf) +leftConf.main(nodeConf) # ------------------------------------------------------------------------------ # End diff --git a/tools/syncer/syncer_test_client_trace.nim b/tools/syncer/syncer_test_client_trace.nim index c8d6d1177d..7fc5d675e9 100644 --- a/tools/syncer/syncer_test_client_trace.nim +++ b/tools/syncer/syncer_test_client_trace.nim @@ -100,13 +100,12 @@ let leftConf = makeConfig(cmdLine = leftOpts) - nodeConf = leftConf.setupExeClientNode() - -# Update node config for lazy beacon sync update -nodeConf.beaconSyncRef = BeaconSyncRef.init rightConf.beaconSyncConfig + # Update node config for lazy beacon sync update + nodeConf = NimbusNode( + beaconSyncRef: BeaconSyncRef.init rightConf.beaconSyncConfig) # Run execution client -nodeConf.runExeClient(leftConf) +leftConf.main(nodeConf) # ------------------------------------------------------------------------------ # End From 70f7e338877022d8f564370a6abe88cc91622b88 Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Tue, 30 Sep 2025 10:55:06 +0100 Subject: [PATCH 18/18] Docu for setting up network tunnel for testing --- tools/syncer/README.md | 312 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 tools/syncer/README.md diff --git a/tools/syncer/README.md b/tools/syncer/README.md new file mode 100644 index 0000000000..13b7952ccc --- /dev/null +++ b/tools/syncer/README.md @@ -0,0 +1,312 @@ +Setting up a routing tunnel from a hidden Rfc 1918 network +========================================================== + +Routing bidirectional traffic selectively through a dedicated tunnel to a +public server (e.g. a non-expensive rented *cloud* system) can emulate many +properties of a publicly exposed system. This can be used for a test system +hidden behind a digital subscriber-line or dial-up network so it can emulate +many properties of a publicly exposed system. + +The systems involved do typically share other services besides the ones +installed for tunnelling. Some care must be taken on the local system when +allowing incoming data connections. This can be mitigated by using network +filters (e.g. `nftables` on *Linux*) for accepting incoming connections only +on sub-systems, e.g. `qemu` or `docker` virtual hosts. + +General layout +-------------- + +The following scenario was set up for testing the syncer + + EXTERN LOCAL SUB1 + +-------+ +-------+ +------+ + | | tunnel | | | | + | o---l----------r---o o---s---+---a---o | + | o | | | | | | + +---|---+ +---|---+ | +------+ + x y | + | | | SUB2 + ----//----+----//------------//--+ | +------+ + internet internet MASQUERADE | | | + +---b---o | + | | | + | +------+ + : ... + +where *EXTERN* is a system with a public IP address (on interface **x**), +fully exposed to the internet (e.g. a rented *cloud* server). *LOCAL* is a +system on a local network (typically with Rfc 1918 or Rfc 5737 addresses) +and has access to the internet via *SNAT* or *MASQUERADE* address translation +techniques on interface **y**. + +The system *LOCAL* accesses services on system *EXTERN* via the internet +connection. An *EXTERN* -- *LOCAL* logical connection facilitated by +interfaces **X** and **Y** allows for setting up a virtual peer-to-peer +tunnel for general IP (UDP and TCP needed) between both systems. This tunnel +is depicted above the dedicated *EXTERN* -- *LOCAL* connection with +interfaces **l** and **r**. + +The system *LOCAL* provides routing services to the internet for systems +*SUB1*, *SUB2*, etc. via interface **s** on *LOCAL*. Technically, these +sub-systems might run on a virtual system within the *LOCAL* system. + + +Example interface and network addresses +--------------------------------------- + +These addresses as used the below configuration scripts are listed here. + +| interface | IP address | netmask | gateway | additional info +|-----------| ----------------:|:--------|:--------------|----------------- +| **a** | 192.168.122.22 | /24 | 192.168.122.1 | +| **b** | 192.168.122.23 | /24 | 192.168.122.1 | +| **l** | 10.3.4.1 | /32 | n/a | point-to-point +| **r** | 10.3.4.2 | /32 | n/a | point-to-point +| **s** | 192.168.122.1 | /24 | | +| **x** | | | | public address +| **y** | | | 172.17.33.1 | dynamic, DHCP + + +Why not using *ssh* or any other TCP tunnel software +---------------------------------------------------- + +With *ssh*, one can logically pre-allocate a list of TCP connections between +two systems. This sets up listeners on the one end of the tunnel and comes out +on the other end when an application is connecting to a listener. It is most +easily set up and provides reliable, encrypted connections. But this does not +the tunnel wanted here. + +In another *ssh* mode, one can build a connection and tie it to a *pty* or +a *tun* device. In the case of a *pty*, one can install a *ppp* connection +on top of that. In either case, one ends up with a pair of network interfaces +that could be used for implementing the **r**--**l** tunnel for the above +scenario. + +Unfortunately, that scenario works only well in some rare cases (probably on +a *LAN*) for TCP over *ssh*, the reason being that TCP traffic control will +adjust simultaneously: the outer *ssh* TCP connection and the inner TCP data +connection (see details on +[PPP over *ssh*](https://web.archive.org/web/20220103191127/http://sites.inka.de/bigred/devel/tcp-tcp.html) +or [TCP over TCP](https://lsnl.jp/~ohsaki/papers/Honda05_ITCom.pdf).) + + +Suitable **r**--**l** tunnel software solutions +----------------------------------------------- + +The software package used here is `quicktun` which runs a single UDP based +peer-to-peer tunnel and provides several flavours of encryption. + +Other solutions would be `openVPN` which provides multiple topologies with +pluggable authentication and encryption, or `vtun` which provides a server +centric star topology (with optional encryption considered weak.) + + +Setting up the **r**--**l** tunnel on Debian bookworm +----------------------------------------------------- + +A detailed description on the `quicktun` software is available at +[QuickTun](http://wiki.ucis.nl/QuickTun). + +All command line commands displayed here must be run with administrator +privileges, i.e. as user **root**. + +Install tunnel software on *LOCAl* and *EXTERN* via + + apt install quicktun + +Generate and remember two key pairs using `keypair` twice. This gives keys + + SECRET: + PUBLIC: + + SECRET: + PUBLIC: + +On *LOCAL* set it up as client. Install the file + + /etc/network/interfaces.d/client-tun + +with contents + + # Do not use the automatic directive "auto tun0" here. This would take + # up this tunnel interface too early. Rather use `ifup tun0` in + # "/etc/rc.local". On Debian unless done so, this start up file can + # be enabled via + # chmod +x /etc/rc.local + # systemctl enable rc-local.service + # + iface tun0 inet static + # See http://wiki.ucis.nl/QuickTun for details. Contrary to the + # examples there, comments must not follow the directives on the + # same line to the right. + address 10.3.4.2 + pointopoint 10.3.4.1 + netmask 255.255.255.255 + qt_local_address 0.0.0.0 + + # Explicit port number (default 2998) + qt_remote_port 2992 + qt_remote_address + + # Available protocols: raw, nacl0, nacltai, salty + qt_protocol nacl0 + qt_tun_mode 1 + + # This is the private tunnel key which should be accessible by + # root only. Public access to this config file should be resticted + # to root only, e.g. via + # chmod go-rw + qt_private_key + + # Server public key + qt_public_key + + # Make certain that tunnel packets can be sent via outbound + # interface. + up route add -host gw 172.17.33.1 || true + down route del -host gw 172.17.33.1 || true + + # Route virtual network data into the tunnel. To achieve this, two + # routing tables are used: "main" and a local one "8". The "main" + # table is the standard table, the local one "8" is used to route + # a set of traffic into the tunnel interface. Routing tables "main" + # or "8" are selected by the policy set up via + # "ip rules add ... lookup " + up ip rule add from 192.168.122.1 lookup main || true + up ip rule add from 192.168.122.0/24 lookup 8 || true + up ip rule add from 10.3.4.2 lookup 8 || true + up ip route add default via 10.3.4.1 table 8 || true + up ip route add 192.168.122.0/24 via 192.168.122.1 table 8 || true + + down ip rule del from 192.168.122.1 lookup main || true + down ip rule del from 192.168.122.0/24 || true + down ip rule del from 10.3.4.2 lookup 8 || true + down ip route flush table 8 || true + + # End + +and on *EXTERN* set it up as server. Install the file + + /etc/network/interfaces.d/server-tun + +with contents + + iface tun0 inet static + address 10.3.4.1 + pointopoint 10.3.4.2 + netmask 255.255.255.255 + qt_remote_address 0.0.0.0 + + qt_local_port 2992 + qt_local_address + + qt_protocol nacl0 + qt_tun_mode 1 + + # Do not forget to run `chmod go-rw + qt_private_key + qt_public_key + + # Route into hidden sub-network which will be exposed after NAT. + up route add -net 192.168.122.0 netmask 255.255.255.0 gw 10.3.4.1 + down route del -net 192.168.122.0 netmask 255.255.255.0 gw 10.3.4.1 + +On either system *EXTERN* and *LOCAL* make certain that the file + + /etc/network/interfaces + +contains a line + + source /etc/network/interfaces.d/* + +Then the tunnel can be established by running + + ifup tun0 + +on either system. In order to verify, try running + + ping 10.3.4.2 # on EXTERN + ping 10.3.4.1 # on LOCAL + + +Configuring `iptables` on the *EXTERN* server +--------------------------------------------- + +As a suggestion for an `nftables` filter and NAT rules set on a *Linux* host +*EXTERN* would be + + #! /usr/sbin/nft -f + + define wan_if = + define wan_ip = + define tun_if = tun0 + + define gw_ip = 10.3.4.2 + define gw_ports = { 30600-30699, 9010-9019 } + define h1_ip = 192.168.122.22 + define h1_ports = 30700-30799 + define h2_ip = 192.168.122.23 + define h2_ports = 9000-9009 + + table ip filter { + # Accept all input and output + chain INPUT { type filter hook input priority filter; policy accept; } + chain OUTPUT { type filter hook output priority filter; policy accept; } + + # Selective tunnel transit and NAT debris + chain FORWARD { + type filter hook forward priority filter; policy drop; + ct state related,established counter accept + iif $tun_if ct state new counter accept + iif $tun_if counter accept + iif $wan_if ct state new counter accept + iif $wan_if counter accept + counter log prefix "Tunnel Drop " level info + counter drop + } + } + table ip nat { + chain INPUT { type nat hook input priority 100; policy accept; } + chain OUTPUT { type nat hook output priority -100; policy accept; } + + # Map new connection destination address depending on dest. port + chain PREROUTING { + type nat hook prerouting priority dstnat; policy accept; + ip daddr $wan_ip tcp dport $h1_ports counter dnat to $h1_ip + ip daddr $wan_ip udp dport $h1_ports counter dnat to $h1_ip + ip daddr $wan_ip tcp dport $h2_ports counter dnat to $h2_ip + ip daddr $wan_ip udp dport $h2_ports counter dnat to $h2_ip + ip daddr $wan_ip tcp dport $gw_ports counter dnat to $gw_ip + ip daddr $wan_ip udp dport $gw_ports counter dnat to $gw_ip + } + # Map new connection source address to wan address + chain POSTROUTING { + type nat hook postrouting priority srcnat; policy accept; + oif $wan_if ip daddr $wan_ip counter return + oif $wan_if ip saddr $gw_ip counter snat to $wan_ip + oif $wan_if ip saddr $h1_ip counter snat to $wan_ip + oif $wan_if ip saddr $h2_ip counter snat to $wan_ip + } + } + + +Running Nimbus EL or CL on *LOCAL* client and/or *SUB1*. *SUB2* +--------------------------------------------------------------- + +When starting `nimbus_execution_client` on *SUB1*, *SUB2*, etc. systems, +one needs to set options + + --engine-api-address=0.0.0.0 + --nat=extip: + +and for the `nimbus_beacon_node` on *SUB1*, *SUB2*, etc. use + + --nat=extip: + +For running both, `nimbus_execution_client` and `nimbus_beacon_node` +on *LOCAL* directly, one needs to set options + + --listen-address=10.3.4.2 + --nat=extip: + +on either system.