Shekyl Stats

Refreshing...

Network

Connected
Seed Nodes Active
--

Chain

Current Block
0
Network Height
—
Top Block Hash
--
Block Time Target
2 min

Rewards

Last Block Reward
0.000000 SKL
Difficulty
0
Estimated Hash Rate
0 H/s

Supply

Circulating Supply
--
Remaining Supply
--
Total Burned
0 SKL

Economics

Release Multiplier
0
Burn Rate
0%
Economy Fee
--
Standard Fee
--
Priority Fee
--

Staking

Stake Ratio
--
Staker Pool
0.000000 SKL
Staker Emission Share
0%
Total Staked
N/A
Staking Height
N/A

Protocol

Transaction Format
TransactionV3
Membership Proof
FCMP++
Spend Auth
Ed25519 + ML-DSA-65
Confidentiality
Stealth + BP+

Node

TX Pool Size
0
Database Size
0 B
Node Version
--
Sync Status
Syncing

Development digest — 21 October 2026

Started by Shekyl Foundation • 0 replies • 20 views

0

Shekyl FoundationAutomated

10/21/2026, 11:17:10 AM

State of dev as of 589473625874, 21 October 2026. Several measurements came back in this window, and in each case the result was used to set a design decision. One feature left the peer-to-peer protocol after it was measured, and the RPC channel's timeouts and CPU limits now come from timings taken on the minimum supported device.

Compression is removed from the peer-to-peer protocol — LANDED

The peer-to-peer protocol Shekyl inherited could compress a message before sending it. The proposal on the table was narrower than removing it: replace the C zstd library with a pure-Rust implementation, as part of the general move of the daemon toward Rust. Before doing that, we measured both codecs on real block and transaction payloads captured from a test daemon, and timed decoding on a Raspberry Pi 4, the slowest device we support.

The results did not support either option. At the setting the protocol used, the C library made every one of the 76 captured payloads larger, not smaller, so in practice nothing was ever sent compressed. The Rust implementation shrank the largest payloads by less than 0.6%, and used 18 to 62 times as much CPU to do it. On the Pi it took 32.5 ms to compress a full block that the C library handled in 0.6 ms. We also measured the peer lists exchanged during the handshake and the periodic sync. Those do compress well, but the saving comes to about 650 bytes a minute per connection, and no code path compressed them in the first place.

Two further reasons settled it. Compressing before encrypting is a size side channel, because the length of a compressed message depends on what it contains, and anyone on the path can read the length. Compressing would also undo the padding buckets, which round messages up to fixed sizes so that their size reveals as little as possible about their content. So compression was removed entirely: the protocol flag, the C++ code, the Rust module, and both zstd crates. A message that still sets the old compression bit is now refused, the same as any other undefined bit. The repetition that made the peer lists compress well comes from the encoding repeating key names in every entry. That is now recorded as follow-up work on a more compact wire encoding, so the saving does not depend on a compressor.

A second deletion followed from the first. Peers had exchanged a "support flags" field so each side could learn whether the other handled compression. With compression gone the field negotiated nothing and was always zero. That field, the message that requested it, and the RPC columns that reported it were all removed. Any future capability negotiation will be designed when the wire format is frozen for genesis. Testnet nodes need to upgrade together for this change.

The RPC channel's handshake gets its limits from measurement — LANDED

We are building a single authenticated, encrypted channel for reaching a daemon's RPC from another machine, so that operators do not have to put an unauthenticated forwarder in front of it. The channel opens with a hybrid post-quantum handshake. That handshake has a cost: the first message a client sends makes the daemon do classical and post-quantum key operations with its long-term keys before it knows who is connecting. On a Raspberry Pi 4, that work measured 641 µs of CPU per attempt at the median and about 0.8 ms at the 99th percentile. A full handshake measured about 2.2 ms.

Two mechanisms now bound that cost, and both are in the code. First, the client's first message carries a short keyed tag derived from the daemon's public keys, and the daemon checks it before anything else. A sender who does not have the daemon's public keys cannot produce the tag, and its message is dropped without a reply after one cheap hash. Measured again on the Pi, rejecting such a message costs 34 µs at the 99th percentile instead of 818 µs. Second, all handshakes share one CPU budget per daemon: a quarter of one core, with each admission charged at the measured 99th-percentile cost of a whole handshake. When the budget is used up, a sender who passed the tag check gets a short authenticated "busy" reply and backs off. Everyone else still gets silence.

The timeouts are the part where the measurement changed the plan. The original proposal was a single 9-second deadline, based on an assumption about transit time over Tor that nobody had measured. We then measured 300 handshakes over an onion connection and 300 over a LAN to the Pi. The registered rule was twice the onion leg's 99th percentile, rounded up to a whole second. That gives two deadlines instead of one: the first message must arrive within 4 seconds of the connection being accepted (onion 99th percentile: 1.8 s), and the whole handshake must finish within 11 seconds (onion 99th percentile: 5.2 s). Both values are compiled into the channel, and a compile-time check guarantees the first deadline always expires before the second.

The listener that will use these pieces is still being built, so the channel itself is not yet something an operator can turn on.

When the bundled Tor is missing or failing, the node says so and does not fall back on its own — RULED

Every Shekyl release ships a pinned Tor, and the daemon manages it. This window settled what happens when that Tor is not usable, because it was deleted, its files do not match the pin, or it fails to start or bootstrap. The ruling is that the daemon still starts. It announces the problem, says why, names the fixes, and repeats the message periodically until the problem is fixed. The fixes are: restore the bundle, point the node at a Tor the operator runs (which is also how bridges are configured), allow clearnet alongside Tor, or switch to clearnet only.

The most important part of the ruling is that the node never makes that last choice for itself. If a node fell back to clearnet automatically whenever its Tor stopped working, then anyone who could block or break that Tor would effectively be choosing the node's transport. So what a node does while its Tor is down depends only on how its operator configured it. A node that has clearnet enabled keeps syncing over clearnet, but holds its own transactions instead of broadcasting them without Tor. A node configured for Tor only waits for Tor and syncs nothing. Running clearnet-only remains a supported choice, for example for an exchange or for a place where Tor itself is risky to use. It is never a default, though, and a node configured that way states the privacy consequence at every startup. The status the node reports over RPC will carry typed values rather than sentences, so every client presents the same meaning.

A second Tor ruling came in the same window. The daemon will keep Tor's public directory cache between restarts, while still discarding guards, keys and the onion address at each boot. A node that bootstraps from scratch on every start looks like a fresh install to its directory guard, while a node with a cache looks like any other Tor client. Keeping the cache also removes most of the slow cold-start tail we measured. Tor verifies the cache itself, so a damaged cache costs one cold boot and cannot influence path selection.

Both rulings are decided but not yet implemented. The daemon work that carries them is next in the Tor supervisor's sequence.

Staking identities lose a key they never used — LANDED

A staking wallet derives separate key sets, called personas, for its staking activity, and rotates to fresh ones to keep stakes unlinkable. Each persona had included a post-quantum message-signing key (SLH-DSA). We traced what read that key and found only one consumer: an encoded receive address for the persona. That address was itself unnecessary. When the wallet funded a stake, it encoded the persona's address only to parse it straight back in the next step, and no persona address ever left the wallet.

The wallet now passes the persona's payment keys directly as an internal destination, and nothing encodes or parses a persona address any more. With the address gone, the signing key had no remaining use, so it was deleted along with its derivation and its domain-separation label. The decision record also lists the alternative we rejected: keeping the key so persona addresses would look uniform with ordinary ones.

The practical effect is on how long a staking wallet takes to open. That key accounted for most of the work of deriving each persona. On a development machine, deriving one persona went from 52.9 ms to 6.71 ms, and a wallet derives several at open. We have not yet re-measured it on the minimum supported device. Wallet files move to a new format version, without a migration, which is normal before genesis.

A stake the network will not take now ends visibly, and its funds come back — LANDED

Before this window, a stake that went wrong was close to invisible. A stake, an emission claim, a drain or an exit becomes a durable pending record in the wallet before anything is sent, and that record reserves the funds it will spend. If the daemon refused the transaction, the wallet wrote one log line, and nothing it reported over RPC said what had happened or when the reserved funds would be usable again.

There were two parts to fixing this, and the second one carries the reasoning. Simply releasing the reservation when a daemon reports a refusal would trust the daemon's word that the transaction will never be mined, and a wallet cannot verify that claim. A more reliable end date was available. Every Shekyl transaction proves membership against a recent block that it names, and a transaction whose reference block is older than a fixed age cannot be included in any block on any chain. So the wallet now holds a refused record, stops resending it, and releases its reservation only once its own scan has passed the point where the transaction could still be mined. It then records how the record ended. The reasoning does not depend on trusting the daemon.

The wallet RPC's staking_info now reports work in flight (what kind of operation, whether it is pending, dispatched, rejected or expired, and the height at which held funds return) and the last outcome for each kind of operation. A restart reports the same answer because it is computed from the wallet's sealed state. A new acknowledge_staking_outcome call, and stake acknowledge in the CLI, clear an outcome once the user has seen it. None of these responses identify which persona or which funding output was involved.

Chain sync's move to Rust is designed, and four tuning options are gone — RULED

Chain sync is the part of the node that downloads and connects blocks from peers. It is one of the largest pieces of inherited C++ still in the daemon, and this window ruled the design for rebuilding it in Rust, with headers verified before bodies. The design covers the sync state machine, the wire messages, how sessions are closed, peer scoring, concurrency and testing. None of it is implemented yet. The first pure-Rust pieces come next.

One ruling has already shipped. The daemon had four operator options for tuning sync: thread count, span size, batch size and maximum download size. The ruling keeps none of them. The batch and size settings are replaced by protocol byte budgets and deadlines. The thread count is replaced by the daemon's shared hashing budget, which is derived from what the minimum supported device can hold. Our view is that a setting which lets an operator tune a node away from the device the protocol is sized for does more harm than good, and an operator on a metered link already has the download rate limit. The configuration code now refuses all four options by name, and each refusal states what replaced it. The old hardcoded defaults stay in place until the Rust sync takes over.


This post describes the state of the dev branch at 589473625874 on 21 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 #1134 (PWD-T7), PR #1137, PR #1139, PR #1128, PR #1121 (RT-P6, RT-P9), PR #1135, PR #1126 (TB-2, TSR-Q8, TSR-Q10, TSR-Q11), PR #1122 (FND-25), PR #1109 (WR-Q1…WR-Q5), PR #1117, PR #1142, PR #1143 (CS-Q1…CS-Q12)

Sign in to reply and vote.