Repository navigation
Conversation
On 2026-10-18 the public testnet had no shared chain. Every gossiped miner block ran a fork-choice pass that decoded the whole store several times and did work quadratic in stored blocks, all under the one datastore mutex. Nothing was pruned, so after 15 days a pass took about five minutes. Status, the Identify handler on the networking task, and Shoal broadcasts all waited on that lock, so peers stopped seeing each other's blocks and node1, node2, and node3 each mined alone. A non-bootstrap miner's unlinkable chain kept feeding the queue. Nothing waits on scoring: - /status.json is served from a snapshot a background task retakes every five seconds; `status_age_secs` shows how old it is, so a wedged node answers with a growing age instead of hanging. No snapshot yet is 503. - Identify stores peer info off the networking task. - Shoal draft, certificate, and ack sends wait at most two seconds for the swarm. Fork choice is incremental: - The datastore keeps an exact in-memory mirror of the three miner stores, updated by every multi_store write, with a change journal. Store scans read the mirror in the same key order, so results do not change. - A tree derived from it keeps each live block's linked work, the winner and canonical spine tips, and the linked branch tips, and relinks only what changed. The target check is memoized per hash and walks back only to the previous epoch. - select_best_stored_chain and adopt_connected_extensions decide on the tree with the same guards and log lines, and fall back to the full scan where it cannot decide. A block off every linked walk is stored without scoring. - Scoring a gossiped block takes about 250us in a debug build at both 2k and 20k stored blocks (test). Randomized tests check select against the full scan, the canonical set against verified_spine, adoption against the rule that no branch beats the chain, and the incremental tree against a rebuilt one. They also show two full-scan bugs the tree does not repeat: a demoted block on the winner left a hole under the tip, and adoption in store order could take a lighter branch and orphan the heavier one. Untrusted gossip is bounded: - Miner blocks are rate limited per forwarding peer (1/s, burst 120). - Parked blocks are capped per author (1024) and in all (8192); the oldest still unlinked is dropped. - Missing parents are requested from the block's author, not only from bootstrappers, at most once a minute per peer. The store stops growing with every fork: blocks that are not canonical two epochs under the latest sequencer checkpoint are pruned, since fork choice already refuses any chain that leaves that prefix. Chain validation indexes blocks once instead of searching per index. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Introduced a caching mechanism for verdicts of `mined_header_holds`, utilizing a `OnceLock` and `Mutex` to store results and reduce redundant computations. - Added a limit to the cache size to prevent excessive memory usage, ensuring efficient management of verdicts during mint checks. - Refactored the `mined_header_holds` function to leverage the new caching logic, improving performance during header validation processes. - Updated the `retarget_params` and `with_synced_tree` functions to public visibility for broader access within the module. These changes enhance the performance of header validation by minimizing repeated calculations and optimizing resource usage.
Upgrading the testnet hosts to the previous commit left all three sequencers answering status with 503 for over ten minutes. A copy of node2's store, run offline, showed why: the MOD minter checks a mint by replaying the contract's whole log, and `mined_headers` grinds every header of every replayed mint through RandomX again. At epoch 385 that is about 15,000 RandomX hashes per mint, under the datastore lock, and it grows with every epoch. Fork choice on the same store took under a millisecond. - `mined_header_holds` is a pure function of the header, index and hash function, so its verdict is kept per process. A mint now grinds only its own headers. The first replay after a start still grinds each header once: about 16 s on the copied store in a release build (it never finished in a debug build before). - The networking loop logged every swarm event at info, pings and Identify included, to the journal and to `logs/node.log`. Those files reached 40-45 GB on the hosts and filled node0's disk. They are debug now. - `examples/replay_store.rs` times the fork-choice, startup, sync, and maintenance reads on a copied store; nothing leaves the machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Refactored the `mined_header_holds` function to improve efficiency by caching verdicts, minimizing redundant computations during mint checks. - Adjusted logging levels for swarm events to reduce disk usage, transitioning from info to debug level, preventing excessive log file growth. - Enhanced performance of header validation processes and reduced the impact of logging on system resources. These changes collectively improve the efficiency of header processing and resource management in the system.
Contributor
Author
|
Landed on main directly: 0c35814 merges 842d45b (testnet release 20261018_182417-842d45b8), and d76ac02 carries the Rust tree of c3f8109 (20261018_201039-c3f81097, tag testnet-mint-memo). The examples/multi-agent-smax commits that rode on this branch are kept on the examples/multi-agent-smax branch. 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On 2026-10-18 the public testnet had no shared chain: no sequencer answered
/status.json, node0 was frozen at tip 4887, and node1, node2, and node3 each mined alone (10196, 15330, 11762) at the difficulty floor, while a non-bootstrap miner gossiped a fourth chain near 18.6k.Every gossiped miner block ran a fork-choice pass that decoded the whole store several times and did work quadratic in stored blocks, under the one datastore mutex. Nothing was pruned, so after 15 days a pass took about five minutes. Status, the Identify handler on the networking task, and Shoal broadcasts all waited on that lock. Peers stopped seeing each other's blocks and each node mined its own branch. This is the third failure of this shape; earlier fixes made steps of the same pass cheaper without removing the per-block rescan.
What changes
Nothing waits on scoring
/status.jsoncomes from a snapshot retaken every 5 s in the background, withstatus_age_secs. A wedged node answers with a growing age instead of hanging. Before the first snapshot exists the endpoint returns 503.Incremental fork choice
multi_storewrites update it, and scans read it in the same key order.It relinks only what changed. The target check is memoized per hash and walks back only to the previous epoch.
select_best_stored_chainandadopt_connected_extensionsdecide on the tree, with the same guards and log lines. They fall back to the full scan for cases the tree cannot decide.Bounded untrusted gossip
Pruning
Tests
verified_spine;cargo test -p modality-node -p modality-datastore -p modality-observer -p modality-cli-node, workspacecheck(including tests and examples) with-D warnings, andclippy -D warnings.tests/network/11-hybrid-devnet3and04-sync-miner-blocksfail the same way onmain:config_version 2.Follow-ups
fills_parentsrule can still adopt a stored chain that contains a block below its target, whenever that chain extends the local one. Peer batches do not check proofs.🤖 Generated with Claude Code