Shekyl FoundationAutomated
10/25/2026, 11:16:22 AM
State of dev as of 5bd0e8e93473, 24 October 2026.
Most of this window went to the node's networking: the new chain-sync code was built and tested against hostile peers, and the daemon now runs and supervises its own Tor. Underneath that, we settled what a node tells a wallet about whether a transaction can leave privately, and replaced a block-size reserve that had been signed off on a claim that turned out to be false.
Headers-first chain sync is built and tested, and not yet switched on — LANDED
When a fresh node joins the network, it has to decide which of the chains its peers offer is the real one, and it should spend as little as possible on chains that turn out to be wrong. The new sync code checks proof-of-work before it downloads anything else. It fetches block headers first, checks how they link, their proof-of-work, difficulty, timestamps and the release anchors, all of which can be done without the block contents. Then it aims for the chain with the most verified work, not the highest claimed height. A peer's claim only decides whom to ask. Block contents come afterwards, and each one is tied to a header that has already been checked before it is passed on.
That ordering has a useful side effect. If a peer serves a valid header and then sends a substituted body for it, the session that sent the bad body is closed, and the header stays valid. One dishonest supplier cannot get honest work thrown away.
This window landed all three parts. The header path and its wire messages came first. Then the sync model: a pure state machine with no networking inside it, tested against stated properties. Last came the runtime around it: the task that owns sync, the executor that does the hashing under a shared budget, serving headers and bodies to other nodes, and a loopback test rig. The rig runs a real node, with a real store and real proof-of-work, against scripted peers. Each misbehaving peer in the rig has its own test: one that lies about its chain, one that withholds, a slow drip, a low-work fork, a forged header, substituted bodies and attestation witnesses, and floods of announcements and requests. In the baseline case a fresh node syncs from two honest peers and hashes each header exactly once.
The rig found two bugs in the model before any of this went live. In one, the model asked for more headers while an earlier batch was still being proven, using a position from before that proof. The peer then re-sent headers we were already holding, and the session closed. In the other, a peer that held exactly our best chain, but had never had to serve us headers, was never asked for block contents. Both are fixed, and each has a regression test.
None of this is in use yet. The daemon builds the new router in pass-through mode, so every peer message still reaches the old C++ handler unchanged. Switching sync over is the next step. When it happens, the inherited handler's sync half is deleted, along with its automatic per-address misbehaviour score. That deletion was decided in the design round: on clearnet, anyone in the middle of a connection can forge a failure, onion addresses cannot be banned, and new addresses cost nothing. So a persistent ban score mostly causes the node to forget honest peers. What actually limits cost is doing cheap checks before proof-of-work, keeping one outstanding request per session, enforcing byte budgets, and giving work the node asked for priority over work it did not.
The daemon runs and watches its own Tor — LANDED
The daemon now starts, supervises and restarts its own Tor process. The old C++ path for managing Tor was deleted earlier this month, and its replacement, written in Rust, landed this window. If Tor dies, the supervisor reaps it, waits under a capped backoff, and starts it again with the same onion address, so peers can still reach the node at the address they already know. Tor binaries are pinned and checked before they run on Linux, Windows and macOS. If the pinned binary is missing or fails its check, the node says so, and it does not run the binary.
The directory cache shows how these decisions are being made. Tor keeps two kinds of state on disk. One is the public directory: the signed list of relays that every Tor client downloads. The other is the client's own: its entry guards, its keys and its onion identity. The daemon now keeps the public directory across restarts and discards everything else. A node that bootstraps from nothing at every start looks like a new install to the Tor network each time. With a cache, it looks like any other Tor client, and it stops fetching from the fallback mirrors and directory authorities on every restart. Guards are kept for the length of one run and no longer. Keeping them across runs would not lower the expected exposure to a hostile guard, and it would let one hostile guard link the node's onion addresses across every run. Only four named files are copied, each must be a regular file under a size limit, and no symlinks are followed. The cache is written back only after a clean stop, so after a crash the previous copy stays as it was. Tor verifies the cache itself, so at worst a bad cache costs one cold start.
A test run during the window found a gap in the supervisor's design. A Tor that hangs is supposed to count as a dead Tor, because a reply that misses its deadline counts as a process fault. But while Tor was running normally, the supervisor asked it nothing, so that deadline never started. In the Linux acceptance run, a Tor process that was frozen deliberately went unnoticed for five minutes, and the node kept routing through it. The supervisor now checks periodically that a running Tor still answers, over the private control connection the daemon holds, which the network cannot see. With that check in place, the same test withdrew the route about a minute after the freeze, and Tor came back with the same onion three seconds later.
The supervisor has not passed its acceptance yet. That means the full test matrix on all three platforms, then a seven-day soak on each, with a Raspberry Pi 4 as the Linux host. We will not describe it as suitable for unattended long-running use before then.
What a node tells a wallet before it sends — RULED
A wallet needs one thing from its node before it broadcasts: will this transaction leave without revealing the node's address? The answer is not the same as "Tor is up". After Tor starts, the link becomes usable before any address-hiding peer has finished connecting. And even with such a peer connected, the node's own transactions take their first hop through a peer chosen in advance for a period, so a session can be live while the node's own transactions still wait.
The Foundation has now ruled how the node reports this. Node state comes in two levels of detail, and the higher level includes everything in the lower. The basic level, which a read-only client may hold, carries one yes-or-no: does an address-hiding session exist right now. The full level, which the node's own CLI and GUI and the wallet preset hold, carries what will actually happen to the node's own transactions: they leave through an address-hiding route, they wait for one to connect, they wait for that first-hop choice to be rebuilt, they go over clearnet, or the node is offline. Wallets decide whether to send from that field, never from the yes-or-no.
The first-hop detail is kept off the basic level deliberately. Suppose a peer can read the basic level and is also the node's chosen first hop. If disconnecting made the flag flip, that peer would learn that it had been the first hop for the node's own transactions, and that links the node to what it sends. The basic level therefore stays true for as long as any address-hiding session exists. For the same reason, Tor's state and timing never appear there, because they would let an outsider tie the node's RPC endpoint to the lifetime of its onion address.
Two further rules come with the ruling. "Clearnet only" means one thing, and only one: the operator turned Tor off and clearnet on. A node whose Tor is down reports that it is waiting to connect, never that it is on clearnet, even if it is syncing blocks over clearnet at the time. The node never falls back to clearnet for its own transactions on its own, because then whoever could block its Tor would get to choose how it sends. And causes and suggested fixes go over the wire as typed values, not sentences. A single shared function turns them into words, so the daemon's log, the CLI and the GUI say exactly the same thing.
The split between "an address-hiding session exists" and "our own transactions leave" is already in the supervisor's code and was merged this window. The wire field names from this ruling have not been implemented yet.
A block-space reserve, derived instead of pinned — LANDED
Shekyl's limit on transaction size has a simple purpose. Any two transactions of the maximum size, plus the block's coinbase, must fit in a block that pays no size penalty. That way no single transaction can claim more than half the guaranteed space, and no transaction can only get in by eating into the miner's reward. So the limit is half the penalty-free block size, minus a reserve for the coinbase.
The reserve was a fixed number carried over from the inherited code, and it was ratified on the stated grounds that a Shekyl coinbase serializes well under it. That was not true. A Shekyl coinbase carries post-quantum key material and other fields that the inherited figure never had to account for, and when it was measured, even a minimal coinbase came out at more than twice the reserve. The ruling was reopened earlier in the month, and it was closed this window.
The reserve is now computed. It is the largest coinbase the transaction format allows, with no added margin: every field at its widest permitted encoding. A test builds that maximal coinbase through the real encoder, weighs it, and checks that the result matches the computed figure, so a format change that moves one without the other fails the test. A new CI check refuses fixed transaction-weight numbers written into the source. The block template now counts the coinbase when it fills a block, and the transaction-size limit moved with the reserve.
The penalty-free block size this all hangs from is still an open design question. When it is settled, the limit will move again, and nothing will need to be edited by hand.
This post describes the state of the dev branch at 5bd0e8e93473 on 24 October 2026.
Shekyl is pre-genesis and under active design; anything marked IN FLIGHT may
change or be abandoned. Where a later post contradicts this one, the later post
is correct.
References: PR #1145, PR #1148, PR #1166, PR #1149, PR #1151, PR #1155, PR #1167, PR #1170, PR #1162, PR #1164, PR #1172, PR #1152 (CS-1…CS-3, TSR increments 3/3b, TB-11, IG Part E, GS-C1, C2-R2 Q7)