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%

Staking

Stake Ratio
0
Staker Pool
0.000000 SKL
Staker Emission Share
0%
Total Staked
N/A
Staking Height
N/A
Tier 0 Lock Blocks
N/A
Tier 1 Lock Blocks
N/A
Tier 2 Lock Blocks
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 — 9 October 2026

Started by Shekyl Foundation • 0 replies • 4 views

0

Shekyl FoundationAutomated

10/12/2026, 10:57:00 PM

State of dev as of e51db66f976f, 9 October 2026. Three days in which several things described as "not built yet" in the last post were built: Tor now ships inside the Linux packages, an archiver signs what it serves with its real key, and the first piece of Monero's upgrade machinery is gone. Alongside that, an earlier ruling on how nodes read history over Tor was reversed, and the smallest supported device was measured doing the serving.

Every read of archived history gets its own Tor circuit — LANDED

Archivers serve the chain's history from onion services, and nodes fetch pieces of it over Tor. A month ago the ruling was that these fetches should present no credentials to the local Tor, on the reasoning that they would then share circuits with the node's ordinary peer-to-peer traffic and blend into it. That reasoning was wrong, and the ruling was reopened this window. A connection to an onion service rides a circuit built to that service alone, and the peer-to-peer layer never connects to an archiver's serving address, so there was nothing to blend with. What the old arrangement did instead was put every read a node sent to one archiver onto the same circuit. The archiver could then tell that a set of reads came from one node, that a retry belonged to the read that failed, and that reads many blocks apart had the same origin.

Each read now presents a username and password to Tor that no other read presents. Tor's default behaviour is to give each distinct pair its own circuit, so no Tor configuration changes. The pair is derived from a random value that every read already carries, inside the request code, so no caller chooses it and no kind of read can be made to look different from another. A retry of a read that stalled repeats the same value and stays on its circuit; a new read of the same piece gets a new one. The client offers Tor only the username-and-password method. A proxy that answered "no authentication needed" would quietly put the read back on a shared circuit, so the connection fails instead.

Tests of the client can show what it sends, not what Tor does with it, and the ruling rests on the second. So it was checked against a real Tor over the public network, by asking Tor's control port which circuit each read used. In every run that reached the comparison, six of eight, the retry stayed on its circuit and the second read used a different one. The other two runs ended early on stalled fetches, not on shared circuits.

This costs something, and the cost has not been measured. A circuit per read means a circuit build per read: the ruling's own estimate is a few seconds of setup each, and more first attempts that fail. The ruling names the measurement that would reverse it. Per-read circuits also separate circuits only. They all leave through the node's one Tor entry relay, which sees that the node uses Tor and how much, as it did before, and does not see where the circuits go.

An archiver signs what it serves with its bond key — LANDED

The last post described the serving code and said that connecting the archiver's signing key to it was still to come. Until that was done, a running archiver had no key to sign with and answered every valid request "unavailable". The key is now connected.

The interesting part is which key and where it lives. The signature a requester checks is made with the archiver's bond identity key, the only key of the archiver's that a requester knows. That same key authorises posting the bond and claiming rewards. Handing a copy to the serving code would put a key with those powers inside the part of the node that listens to the network, with only an API convention saying what it may sign. So the serving code holds no key. It holds a reference to the wallet component that does, sends it the short transcript of what was just delivered, and gets a signature back. On that path the component signs only that one kind of message.

The reference is deliberately a weak one. Closing the wallet wipes the key, and the serving code cannot keep it alive past a signature already in progress. Once it is gone, new requests are refused before any data is read. The test that proves the wiring puts the real key behind the real serving endpoint, fetches with the real client, and verifies the signature under the bond identity. It then repeats the fetch expecting a different wallet's identity and requires the client to reject it, so the first half cannot pass against a client that does not check.

The signature is today's hybrid of Ed25519 and ML-DSA-65. The separate, smaller receipt key ruled last window is not built.

What serving history costs on a Raspberry Pi 4 — LANDED

The project's minimum supported device is a Raspberry Pi 4, and a ruling this window extended that floor from running a node to serving history as well: either the design fits on the Pi, or the record says where it fails and the minimum is set from the failure. Two measurement sessions followed. The criteria and predictions for each were committed before the run.

The first session asked whether the Pi can serve at all, next to a node that is idle. It can. A full piece of history takes about 52 ms to serve, and the board sustained ten times the expected honest demand for an hour without warming past 55 °C. The session also tested a change that then merged. Hashing what is delivered is the most expensive step, and it had been running on the threads that also keep the serving code's timers; under eight simultaneous requests those timers fired up to 57 ms late at the 99th percentile. Moving the hash to the thread that reads the data from disk brought that to under 6 ms and made throughput steady at about 52 responses per second, where it had swung between 41 and 56. One of the four criteria failed as it was written, because it had been written against a response size the node never serves. On the size it does serve, the criterion holds. The record keeps the original wording and the failure beside the corrected reading, and lists what the result does not cover.

The second session put a node syncing the whole chain next to the serving code, which is when the two compete for the board. Serving held up better than predicted: 37 to 41 responses per second, about 70% of its idle rate, with timer lateness under 61 ms at up to 64 simultaneous requests. The node did not hold up. It kept about a third of the sync rate it has when nothing is being served. Running the serving process at the lowest scheduling priority gave the node back 90 to 97% of its sync rate, and serving dropped to between 5 and 8 responses per second. Five of seven predictions held, and the two misses were both the device doing better than expected. The first reading of the sync figures was too favourable; review found that each measurement window had borrowed a data point from the one before it. The numbers here are the corrected ones.

All of this was measured on the device's own loopback interface, without Tor, which is far slower at taking the bytes. The second session was explicitly a discovery run: it grades nothing and does not set the limit on simultaneous requests, which stays an unmeasured placeholder. What it does establish is that how a small device shares its cores between syncing and serving is a real design question, with numbers now attached.

The block header's spare version byte is pinned to zero — LANDED

Every block header carries two version bytes. In Monero the second one is a vote: miners use it to signal readiness for a network upgrade, and the node counts votes over a window of recent blocks. The last post reported the ruling to delete that inherited upgrade machinery. Deleting a mechanism does not delete the byte it used. With the vote gone and nothing else checking it, the byte would have been a field that any block producer could fill with any value and every node would accept. The ruling that came with the deletion was to pin it to a single value and validate it.

The value is zero, which is what the genesis block already carries. Both validators, the C++ one and the Rust one, now refuse a block with any other value, on the main chain and on side chains, and both block-template builders write zero without taking the byte from their caller. Because this is a consensus rule, the four changes landed together in one commit. Mined blocks on test chains had been carrying a 1 there, which turned out to be the upgrade table handing its version number to the template.

The voting code went in the same change set: the vote window, the threshold, and the decoder that read a zero as a vote for version 1. So did the three per-network copies of the upgrade schedule. They were the same single row three times, with the network type choosing which copy to load, and there is now one. The rest of the mechanism is still present: the first version byte is still checked through the one-row table. Removing that table, and stating the rule as a plain comparison, is the next change.

Tor ships inside every Linux package — LANDED

The last post reported the ruling that a Shekyl node has Tor unless its operator explicitly turns it off, and that every release package would carry a pinned copy. The Linux step of that is merged, for both x86-64 and 64-bit ARM, and for every package type: the archives, the .deb and the .rpm.

The packaging takes the Tor Project's bundle as a fixed input and compares its hash with the recorded one before opening it. It extracts only the tor program and the three libraries it loads, checks each file against its own recorded hash, and puts them in a directory of their own, with Tor's licence texts and a pointer to the signed source alongside. The recorded hashes live in one file that both the build and the packaging read, and a build for a platform with no entry in that file does not compile.

At startup the node checks more than the tor program. A verified program that loads an unverified library proves little, so each library is checked too. The directory must also contain exactly the recorded files and nothing else, since the system's loader will pick up a library it finds there whether or not anyone listed it. The node then starts Tor with an empty environment, apart from the one variable that points the loader at that directory. Looking for a tor on the system's search path is removed, because a Tor found that way has its libraries somewhere else and could never pass.

A node whose operator has declined Tor now says so in a warning every time it starts. A node that simply finds no Tor also warns, where it used to log a line most operators never saw, and it still starts. Turning that into a refusal to start is the last step and is not built. Windows and macOS do not have the bundle yet; each needs a test showing that the pinned Tor starts under the node's launcher on that platform. FreeBSD operators install Tor themselves.


This post describes the state of the dev branch at e51db66f976f on 9 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 #999, PR #990, PR #997, PR #995, PR #1010, PR #989, PR #994

Sign in to reply and vote.