Summary
DApp (shader app) operations — both read-only calls and transaction building — are gated on the wallet being fully synced. After every new block (Wallet::OnNewTip), the wallet posts sync requests, so SyncRemains() > 0 until the node answers them. Any shader operation started during that window is deferred into m_SyncActionsQueue and only runs once sync settles.
With fast block times (e.g. ~3s), this opens a "not synced" window on every block, so dapp operations — including pure reads — stutter roughly once per block.
Where it happens
All paths below are in wallet/core/:
contracts/shaders_manager.cpp: CallShader (read-only, ~L265) and CallShaderAndStartTx (tx, ~L246) both funnel through pushRequest → nextRequest(), which wraps the shader start in m_Wallet.DoInSyncedWallet(...) (~L328).
wallet.cpp:
DoInSyncedWallet (~L522) runs the action immediately iff !SyncRemains() && IsNodeInSync(), otherwise pushes it onto m_SyncActionsQueue:
bool bSynced = !SyncRemains() && IsNodeInSync();
if (bSynced) { AsyncContextHolder holder(*this); action(); }
else { m_SyncActionsQueue.push(std::move(action)); }
OnNewTip (~L2233) fires on every block and posts RequestBodies() / RequestEvents() / RequestStateSummary() before CheckSyncDone().
SyncRemains (~L2332) counts pending Utxo/Kernel/Events/StateSummary/BodyPack/Body requests (REQUEST_TYPES_Sync in wallet.h).
- The deferred queue is flushed only in
SaveKnownState (~L2371), reached via CheckSyncDone once SyncRemains() == 0 and the tip timestamp passes IsValidTimeStamp — so a stale tip blocks the flush as well.
Impact
- Recurring deferral synchronized to block arrival — ~20×/min at 3s blocks.
- Worst on the common integrated-node + owner-key setup, where all three request types (
RequestBodies requires the integrated/mobile node; RequestEvents requires an owned node online) fire on every tip.
- Read-only shader calls are gated identically to transactions, even though a slightly-stale read is usually harmless. This is the likely source of the perceived "paused every block" behavior.
- Per-block window length ≈ node round-trip to satisfy the sync requests: short on a local node, but can stall noticeably under network latency or when the node streams blocks in a burst (catch-up).
Suggested direction
- Let read-only
CallShader bypass DoInSyncedWallet (or use a relaxed sync check), keeping the strict gate only for CallShaderAndStartTx, where building against stale state risks an invalid kernel.
- Add enqueue/flush timing logs around
m_SyncActionsQueue to quantify the per-block window before deciding on the fix.
Summary
DApp (shader app) operations — both read-only calls and transaction building — are gated on the wallet being fully synced. After every new block (
Wallet::OnNewTip), the wallet posts sync requests, soSyncRemains() > 0until the node answers them. Any shader operation started during that window is deferred intom_SyncActionsQueueand only runs once sync settles.With fast block times (e.g. ~3s), this opens a "not synced" window on every block, so dapp operations — including pure reads — stutter roughly once per block.
Where it happens
All paths below are in
wallet/core/:contracts/shaders_manager.cpp:CallShader(read-only, ~L265) andCallShaderAndStartTx(tx, ~L246) both funnel throughpushRequest→nextRequest(), which wraps the shader start inm_Wallet.DoInSyncedWallet(...)(~L328).wallet.cpp:DoInSyncedWallet(~L522) runs the action immediately iff!SyncRemains() && IsNodeInSync(), otherwise pushes it ontom_SyncActionsQueue:OnNewTip(~L2233) fires on every block and postsRequestBodies()/RequestEvents()/RequestStateSummary()beforeCheckSyncDone().SyncRemains(~L2332) counts pendingUtxo/Kernel/Events/StateSummary/BodyPack/Bodyrequests (REQUEST_TYPES_Syncinwallet.h).SaveKnownState(~L2371), reached viaCheckSyncDoneonceSyncRemains() == 0and the tip timestamp passesIsValidTimeStamp— so a stale tip blocks the flush as well.Impact
RequestBodiesrequires the integrated/mobile node;RequestEventsrequires an owned node online) fire on every tip.Suggested direction
CallShaderbypassDoInSyncedWallet(or use a relaxed sync check), keeping the strict gate only forCallShaderAndStartTx, where building against stale state risks an invalid kernel.m_SyncActionsQueueto quantify the per-block window before deciding on the fix.