Shekyl FoundationAutomated
10/18/2026, 11:14:34 AM
State of dev as of 0f9e897c6d9b, 18 October 2026.
This window was mostly about moving ownership. The daemon's configuration and its process entry moved to Rust. The fee arithmetic was consolidated into one Rust owner. The design for retiring the daemon's inherited C++ chain store was ruled. Alongside that, we deleted simulation code that measured designs we no longer run.
The fee rule has one owner, and the wallet no longer asks the daemon how to round — LANDED
A transaction's fee is checked in two places. The wallet computes a fee before it signs, and every relaying node checks that the fee clears a floor before it passes the transaction on. If the two sides round differently, a wallet can build a transaction that its own node refuses, and the user sees a failure they could not have prevented. Until this window the rounding rule had several copies: one in the inherited C++, one in the Rust economics crate, and one in the wallet's client library. The C++ copy was also handed to the wallet at run time as a field in the fee-estimate response, so the wallet rounded according to whatever the daemon told it.
The rounding step is now a single Rust constant, derived from the coin's display precision, plus one function that rounds a fee up to it. Relay admission calls that function, and so does the wallet's fee arithmetic. The client library now depends on the economics crate rather than keeping its own copy (rust/shekyl-economics/src/fee/quantization.rs, rust/shekyl-economics/src/fee/relay.rs). The daemon no longer serves a rounding value. The fee-estimate request no longer takes a parameter either: the old grace-period argument was deleted, and a request that still sends it is refused, not silently ignored. We refuse it because a client that believes it is getting a behaviour it isn't getting is worse off than one that is told plainly.
The relay floor itself is now computed in Rust from one block's inputs, read straight from the chain store. Nothing is cached, so the result after a chain reorganisation is correct by construction rather than by careful invalidation. That composition is built and tested: across six captured test chains, 8,760 floors match an independent recomputation. It is not yet what the running daemon admits against. It is wired in as part of the store change described below, so that half is IN FLIGHT.
Several pieces of fee machinery left with this change. Two quantizers went: a power-of-two rounding step and a money-rounding helper. Both existed to smooth over effects that the current design no longer produces. Their removal also tightened the wallet's absolute fee cap to the exact structural maximum, replacing a value that had been rounded up to the next power of two.
Deleting the instruments of designs we no longer run — LANDED
The economics simulator is how we test fee and staking designs before they are built. Over time it accumulated tools for designs that were later replaced. Those tools kept running and printing figures, which made it easy to read a number about a mechanism that no longer exists and treat it as current.
This window removed several of them. The best example is a "dwell" instrument. It counted how often the fee ladder's whole-number rung changed from block to block, and reported frequent changes as instability. Once the fee correction moved to an exact moving average, the correction shifts slightly on almost every block, which is what the design intends. The instrument counted how often the rung changed, not by how much, so it read "unstable" exactly when the design was working. The question it was built to answer is now covered by a measurement in basis points of the per-block move. So the instrument was deleted rather than recalibrated.
Also deleted: a reproduction of an earlier public challenge-issuance scheme, which a secret per-block draw has since replaced in production; a simulated race that counted quotes a relay would later refuse, under an admission rule that was later re-ruled; a comparison column that still described itself as "today's daemon output" when that had stopped being true; and a second simulation loop that ran the same experiment as another loop. Duplicate experiments drift apart over time, so one of them had to go. The design documents keep the dated figures these tools produced. The code that produced them is in git history and no longer in the tree.
The fee-ladder simulation now scores convergence against a single bar that was fixed in advance. It also names one specific, explained effect separately: where the floor is very small, the served rate's whole-unit resolution makes the result toggle between two adjacent values. Under the old counting method, 323 of 600 grid cells read as oscillating. Under the new one, 18 do, and each of those 18 is that resolution effect.
Retiring the inherited chain store — RULED
The node still runs the inherited C++ blockchain class, its LMDB database, and its transaction pool. A Rust store, Rust validator and Rust connector have been built beside them. The open question was how the switch happens. One option was to put the Rust store underneath the existing C++ class. The finding that settled it was that the C++ class is not just storage. It validates, it writes, and it keeps caches that only its own block-adding path fills. Putting a new store under it would leave those caches stale, and the C++ validation and template-building would keep running next to the Rust validator. That is two validators, and two validators eventually disagree.
The ruling deletes the C++ blockchain class, its database layer, LMDB and the C++ transaction pool, all together. The parts of the C++ that have no Rust owner yet, namely the peer protocol handler and the relay notifier, keep running behind a thin shell. Each shell method forwards to Rust, and each will be deleted when its owner lands. A sync question that needs several chain facts is answered by one Rust call over one consistent snapshot, so no decision is made from two different views of the tip. The C++ RPC server moves to Rust in the same change, every handler at once, so that no interface is built for C++ code that would be deleted a release later. The built-in miner becomes a Rust loop over the same pure-Rust RandomX hasher the node already uses for verification.
The ruling also defines what must be true before the change can merge. The goal is equivalent functionality built to Shekyl's design, not behaviour-for-behaviour parity with the C++. Every observed difference between the two is recorded and placed in exactly one category: a Shekyl design decision, a defect in the C++, a mechanism Shekyl deliberately removed, something staged with a named owner, or a defect in the Rust. Only the last one blocks the merge, and a difference that cannot be placed blocks it too. There is no "unexplained" category.
Part of the groundwork landed this window. The Rust store can now switch to a heavier competing branch. While building that, we found that a switch was recomputing proof-of-work for every block it re-applied, even though every one of those hashes had already been computed when the block was first judged. The store now records each block's proof-of-work result beside it. A switch reads the recorded result instead of hashing again. On a 16-thread development machine, the measured switch went from 114 seconds to 2.7 seconds. The rest of the change (the Rust pool, the arrival router, the RPC handlers) is IN FLIGHT.
The daemon's configuration and process entry moved to Rust — LANDED
The daemon's command-line and config-file handling was inherited C++, and so was its startup orchestration. Values were parsed in C++ and then pushed across to the Rust components that used them. This window moved all of that into Rust in one change. The executable's main now makes one call into Rust, which owns the process, the signal handling and the order things start in (src/daemon/main.cpp, rust/shekyl-daemon/). Each Rust component receives its section of a typed configuration when it is constructed. The C++ that still needs values reads a read-only snapshot that Rust fills, and that snapshot is only allowed to shrink.
Some of what operators will notice:
- The configuration file is TOML. It is parsed and validated in full before anything starts. One bad line means nothing starts, with a diagnostic that names the problem. There is no environment-variable layer.
- The daemon checks who can write the file and every directory above it before it reads a single value. If the file holds a secret, it also checks who can read it. On Windows this is an allowlist of accounts permitted to write, not a list of accounts that are refused (
rust/shekyl-daemon-config/src/file.rs,rust/shekyl-win-sec/src/dacl.rs). - A data directory now carries a small marker recording which network it belongs to. A daemon started on the wrong network, or on data left over from an earlier test chain, refuses and says which fix applies, instead of quietly using the directory (
rust/shekyl-daemon-config/src/marker.rs). - Pre-genesis, removed options are not kept as compatibility shims. The table of retired flags, the sample config file and the UPnP library are deleted. UPnP was dropped because the managed onion service already gives a node inbound reachability without opening a port on the operator's router.
Mining-related options that change at run time go through the mining commands, and the other runtime changes go through one shared path. A few items are left as tracked follow-ups, including SOCKS proxy credentials and the blockchain utilities, which keep their own option sets for now.
The managed Tor reads only the configuration the daemon gives it — LANDED
The daemon and the wallet can launch and manage their own Tor process. When Tor is not told otherwise, it also loads a default configuration file, a defaults file and GeoIP data from locations fixed when that Tor binary was built. It does this even when the launcher passes its own settings. Whatever was in those files merged into our Tor's configuration.
The managed launch now names its own configuration, defaults and GeoIP files explicitly. All four point at one empty file that the launcher writes into its own data directory before each start. It also clears the environment and points OpenSSL's configuration at a file of its own, so nothing outside the daemon's data directory contributes to how its Tor behaves (rust/shekyl-tor-control-client/src/control/launch.rs). A live test reads Tor's own log and checks that every configuration file it opened was the launcher's.
The same work found that on Windows, Tor launched with a cleared environment fails to start, because it needs one system variable. The launcher now sets that variable from the operating system's answer rather than inheriting it from whoever started the process. This was fixed before any Windows build ships a Tor binary.
What a user will see when a staking transaction is in flight — RULED
Staking actions (a stake, a reward claim, a drain, an exit) each become a saved pending record in the wallet before anything is sent, and each record holds the funds it needs while it waits. Until now the wallet's RPC reported none of these records. A stake that was waiting, or one that would never land, looked the same as no stake at all.
The ruled design adds two required lists to the staking status call. The first, in_flight, has one entry per live record: what kind it is, and whether it is waiting to be sent, has been sent, has been refused, or has expired. The second, outcomes, keeps the last result for each kind once a record has ended, with a plain-language reason and whether funds were returned. The reason is a sentence, never an internal error name. An empty list means nothing is pending; no field is left absent or null to mean that. Every state is derived from records saved on disk, so it reads the same after a restart.
Every such transaction is built against a recent block, and the network refuses one whose reference block is older than a fixed limit. So each record has a definite end date. Past that height no block on any chain can include it. The ruling uses that: once the wallet's own reorganisation-safe scan reaches the end date, the record ends, its funds are released, and the outcome is written in the same save. This costs no fee and needs no action from the user. One alternative was rejected: cancelling by spending the held funds elsewhere. That would cost a fee and would create an on-chain event tying the cancellation to the original attempt, and the end date already gives the same finality for free.
This is ruled and not yet built. The implementation is a separate step, with the ruling's acceptance tests as its bar.
This post describes the state of the dev branch at 0f9e897c6d9b on 18 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 #1096, PR #1087, PR #1078, PR #1101, PR #1082, PR #1066, PR #1088, PR #1111, PR #1083, PR #1093, PR #1089, PR #1079