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 — 14 October 2026

Started by Shekyl Foundation • 0 replies • 6 views

0

Shekyl FoundationAutomated

10/14/2026, 11:12:19 AM

State of dev as of ebb4dc354f9d, 14 October 2026. Most of this window went to one design question in archival serving: how the proof that a server answered gets onto the chain. We also measured how the daemon shares a machine with a wallet that serves archival data, and changed the packaging because of what we found. Two smaller pieces of settled work round it out: the rules for every option a node operator types, and a cleanup of the daemon's main status call.

Serving proofs travel as one object per block — RULED

Archival nodes in Shekyl are paid for keeping old chain data and serving it on request. To check that they actually do, each block secretly draws a small set of servers to be asked for specific data. Afterwards the block's producer reveals the draw, along with a record for each server that answered. A server that was drawn and has no record has missed. This means the reveal and its records must reach the chain together, intact, and must survive a reorganisation. Otherwise a server can be marked absent for reasons that have nothing to do with it.

The earlier design carried all of this in an ordinary transaction, called a carrier. When we looked closely at how a carrier would actually move through the network, two problems came out. First, a carrier paid no fee, and nothing in the node, in either the inherited pool or the new Rust submission path, relays a zero-fee transaction or takes one back after a block is popped. The carrier had no way to travel. Second, a busy block might need more than one carrier, and the draw was issued by whichever carrier arrived first. Whether a server's answer counted could then depend on whether a second transaction was ever included. That is not a property you want a penalty to depend on.

The carrier has been replaced. Each block now has exactly one bundle: the reveal, a bitmap with one bit per draw, and one record per bit that is set. A block admits the whole bundle or none of it, so the chain never holds a revealed draw with its records still pending. Only the producer can author a bundle, because the producer's signature covers every byte, but anyone can relay one. Bundles sit beside the block rather than inside it, are fetched by reference, and wait in a queue of their own instead of competing with fee-paying transactions. The part that settlement needs is kept permanently. The receipts and signatures are archival data and can be pruned once their dispute window has closed.

Two pieces of this are now merged. The key a server signs its receipts with is now a required part of its bond, and it is a post-quantum key. The rule that checks a bundle is merged as well, but it is deliberately inactive: no block path asks it anything yet. Switching it on is a separate, later change, so nobody running a node today sees any difference.

A measurement changed how the daemon is packaged — LANDED

A staker running a headless node may also run a wallet that serves archival data from the same machine. Serving must never slow down block verification. The mechanism we ship for this lowers the priority of the wallet's serving threads to the lowest the scheduler allows. A first benchmark showed this works. We then ran a confirming benchmark in two layouts, and it qualified that result.

With the daemon and the serving process in the same control group, the daemon kept 91–93% of its normal sync rate while serving, against 38% with no priority change. That matched the first benchmark. With the daemon in a separate control group, which is how a system service actually runs, it kept only 65%. Thread priority only ranks threads inside one group. It has no effect between groups, and the serving group simply took its fair share. We registered the predictions before the run, and the three that concerned the separate-group layout all missed in the same direction.

So the shipped service unit now runs the daemon in its own top-level systemd slice, weighted against the user slice. Under contention the kernel gives the daemon's group a fixed larger share, roughly four-fifths. The wallet stays a per-user process, not a system service, because custody is per-user. We have not measured that weight yet; we chose it by reasoning. The next measurement, block verification time while serving, with and without the slice, decides whether it stays, changes, or goes. The Debian packages install the slice and reload systemd on install and upgrade. The old example unit inherited from the Monero tree was unhardened, sent its output nowhere, and nothing used it. It has been deleted.

The same run turned up a second problem. Two serving responses in 1,536 were cut off as stalled, even though every one of those clients was still reading. The stall timer measured the whole write, so a serving task that was waiting for CPU, which is exactly what low priority makes it do, was blamed for its own lateness. The timer now only covers time the socket refuses data. A task that wakes late and finds room writes without penalty.

One grammar for everything an operator types — RULED

Every name a person types or a script depends on, in every Shekyl binary, now follows one set of rules: command-line flags, config-file keys, command words, keywords and units. The rules were ratified this window. No code implements them yet. They will arrive with the daemon-configuration cutover.

These are the rules an operator will notice. Every option has exactly one name: no aliases, no short flags, no abbreviations. No name is a negation, so a switch is off by default and a capability that is on by default is chosen from a named set. No option value comes from an environment variable. Default paths come from the operating system's account interfaces, not from HOME or XDG_*. Paths must be absolute, numbers are integers, and sizes use IEC units only (MiB, not MB). Peer addresses are IP addresses or onion addresses, never hostnames, because resolving a hostname leaks a DNS lookup on a node meant to be Tor-only. Secrets are read only from files, and the startup line shows them only as "set". Development options are hidden from help, refused on any network that does not list them, announced at every start, and never accepted on mainnet. If mainnet genuinely needs a lever, it gets a public, documented name.

Every refusal is a typed value with a stable code. It is rendered as a message that says what was wrong, where, and what to do. A configuration error exits with a different status from a runtime failure. Help text, shell completion, documentation tables and an annotated example config are all generated from one table per binary, so they cannot drift apart.

Shekyl is pre-genesis, so there is no compatibility layer: a renamed option leaves no alias behind. When the cutover lands, scripts and config files written against today's option names will need updating.

The daemon's status call says each thing once — LANDED

get_info is the call that wallets, monitors and the command-line tool use to ask a node what state it is in. It had picked up redundancy: the same difficulty in three encodings, block size under two names, and the network named four ways, once as a string and three times as booleans. When two copies of one value can disagree, every reader has to decide which one to trust. This window removed the duplicates. Difficulty is now a single hex string, read by a strict parser. The old one accepted a sign, so 0x+ff read as 255, and only the comparison with the other copies had been hiding that. The network is named only by get_version, which is where a node already states its identity. The command-line mining check now reads that same identity, so it refuses a daemon on the wrong network, RPC version, rule set or genesis, not just the wrong network.

The total coins generated and the total burned are now decimal strings of atomic units. This is the encoding the wallet RPC already uses for every amount, so one type serves both. As JSON numbers, these amounts lose precision past 2^53 in any JavaScript reader, and supply grows well beyond that. Both fields are required: a missing field fails to decode instead of being read as zero.

Missing values no longer show up as zeros. If the chain store ever recorded more coin burned than generated, the old reply reported a 0% burn, which is a legitimate value and so looked believable. get_info now refuses with an error that names the broken invariant and gives the three numbers involved. The command-line tool prints "unreadable" for a height or count that did not arrive, instead of 0, which would have looked like an empty chain.

These are breaking changes to the RPC, and each one bumped the RPC version. Anyone parsing get_info directly should switch to the retained field names and the string encodings.

A post-quantum handshake for remote RPC — LANDED

The daemon's RPC listens only on loopback today. The ratified design for reaching it from another machine the operator owns is a single authenticated, encrypted channel for every RPC leg. That channel needs a handshake, and the handshake is now merged. It is a hybrid Noise pattern, Noise_hybridXK_25519+MLKEM768_ChaChaPoly_BLAKE2s: each side is authenticated by key agreement with both classical X25519 and post-quantum ML-KEM-768. An attacker would therefore have to break both to impersonate either end. Its four messages, its handshake hash and its derived transport keys match an independent implementation byte for byte, on the first run, and the comparison was also run against two deliberate errors to confirm it fails on them.

The first message is read before anyone is authenticated, so the handshake is strict about what a stranger can make the daemon do. Every way that message can be malformed produces the same single error, and the daemon sends nothing back. A degenerate key is refused before it is used. The client's post-quantum key is checked as FIPS 203 requires before the daemon encapsulates to it. Secrets are wiped when they are dropped.

The daemon does not use the handshake yet. The listener and the record layer are later steps of the same plan.


This post describes the state of the dev branch at ebb4dc354f9d on 14 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 #1040, PR #1046, PR #1047, PR #1049, PR #1051, PR #1053, PR #1054, PR #1067, PR #1057, PR #1039 (SCS-P14–P40, CEN-J28–J37, FN-DSA increments 3–4, BA-T5 session 3, SH-3, IG-1–IG-15, RK-5c, RT-W9)

Sign in to reply and vote.