solana-foundation

Activity

Stars and mentions · Last 30 days, Oct 7 – Nov 5

GitHub stars8
Mentions76
02468Oct 7Oct 21Nov 5
UTC days · repositories solana-foundation owns2 repositories

Top repositories

solana-foundation’s repositories ranked by new forks, as on trending.

Loading

Contributes to

Repositories outside solana-foundation’s account where solana-foundation opened or reviewed pull requests in the past 365 days.

Who noticed solana-foundation

Everyone who starred or forked solana-foundation’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

82posts
Nov 69 posts
  1. Solana Shred Recovery: When Valid Shreds Form an Invalid Erasure Batch Originally published on xroot.dev. A Solana block reaches validators as shreds, in batches of 32 data shreds and 32 coding shreds tied together by one Merkle root that the leader signs. Every shred in a batch can carry a valid signature and a valid Merkle proof while the batch itself is wrong, because nothing in the signature says the coding shreds are the parity of the data. SIMD-0504 says the rules for a batch apply to rebuilt shreds as much as to received ones, and between 24 October and 5 November Agave rebuilt its recovery path to enforce that: every validator now rebuilds every batch, so a batch like this fails everywhere. We modelled the old and new paths with the Reed–Solomon library Agave uses and ran four kinds of malformed batch past simulated validators losing packets. Under the old code, what a validator ended up doing depended on which shreds it happened to receive. Under the new code, every validator that collects 32 shreds rejects the batch: none of 800,000 rebuilds of a malformed batch passed. - * * How an erasure batch is built The leader cuts its block into data shreds and, for every 32 of them, computes 32 coding shreds with a Reed–Solomon code. Any 32 of the 64 are enough to rebuild the other 32, so a validator can lose half of a batch to the network and still have the block. Then the leader hashes all 64 shreds into a Merkle tree, signs the root and attaches to each shred its path to that root. A validator checks each shred on arrival: the signature over the root, and the path from the shred to the root. Shreds per batch — 32 data + 32 coding Needed to rebuild the batch — any 32 Payload: data · coding shred — 1,203 · 1,228 bytes Erasure-coded part of each — 987 bytes Merkle tree — 64 leaves, 6 levels - * * What the signature does not cover The Merkle root commits to the bytes of each of the 64 shreds. It says nothing about how they relate. A leader can compute its coding shreds from different data, or change one byte of a shred after encoding, then build the tree and sign. Each shred still verifies on its own. The batch is not a Reed–Solomon codeword: rebuilding it from different subsets of shreds gives different answers. Until 29 October, Agave ran Reed–Solomon only when data shreds were missing and at least 32 shreds of the batch were present, tried again after every new insert of shreds, and logged a failed attempt at info level before moving on. The Merkle check made sure a rebuilt shred was byte-for-byte what the leader had signed, so a bad batch could never smuggle in different block data. But whether a validator noticed anything depended on which shreds it held. In our simulation of 2,000 validators per row, adding one shred per insert, each row counts the validators that completed the batch from data shreds alone · recovered without noticing the bad shred · were left waiting for repair: First coding shred corrupted, 1% loss — 1,492 · 0 · 508 First coding shred corrupted, 5% loss — 379 · 0 · 1,621 Last coding shred corrupted, 1% loss — 62 · 1,934 · 4 Last coding shred corrupted, 5% loss — 29 · 1,895 · 76 Those in the first group never looked at a coding shred. With the first coding shred corrupted, every recovery that used it failed, about 31 failed attempts per validator even with no loss, and at 5% loss four validators in five could not complete the batch from Turbine. Repair fixes that, because it hands out the shreds the leader signed, but it takes longer. With the last coding shred corrupted, most recoveries never touched it and quietly succeeded. A corrupted data shred, or coding shreds computed from other data, behaved like the first case. Nothing in any row was rejected. - * * SIMD-0504: the batch is the unit of validity SIMD-0504, written by Philip Taffet of Firedancer, replaces the shred rules each client inferred from Agave’s code with a written list. Sixteen checks apply to each shred: index ranges, the fixed 32:32 layout, a Merkle tree of exactly six levels, flag and size rules. A shred that fails them is dropped. Eight checks apply to the batch as a whole: one slot, one shred version, one chained Merkle root, one parent across the data shreds, padding only at the end. A block containing a batch that fails them is invalid. Under TowerBFT a validator must not vote for it, and under Alpenglow it must vote skip. None of the sixteen or eight checks says the coding shreds must be the parity of the data. That follows from the proposal’s note on recovery: the batch checks apply to received and rebuilt shreds alike, so a validator that received all 32 data shreds must still check the coding shreds. The proposal says why: to eliminate any divergence based on whether a validator received or recovered a shred. A rule that only the validators who happened to need recovery could check would split the cluster the first time a leader broke it. Agave enforces the note by rebuilding every batch, and a batch whose coding shreds aren’t the parity of its data cannot be rebuilt to the root the leader signed. - * * “Always recover”: how Agave checks every batch Agave landed the change in a series of pull requests: #16194 wired the rebuild into blockstore on 29 October, #16378 added the feature gate and enforcement a day later, and #16639 stopped retrying a batch that cannot be rebuilt once SIMD-0504 is active. In the code at the time of writing, a batch is rebuilt exactly once, in the insert that first brings it to 32 shreds, whether or not any data shred is missing: received exactly 32: reconstruct the other 32 shards received more than 32: reconstruct missing data from 32 of them, re-encode all 32 coding shards, compare with the coding shards received then: rebuild the Merkle tree from all 64 leaves, compare with the signed root fail either check: with SIMD-0504 active, mark the slot dead (unless it was already full before this insert) The gate is stricter_shred_validation, key s5o4YoA9zUrw9j4v335ncdG5PpznDBsrgM6CvUGrasA. On 6 November at 03:18 UTC its feature account did not exist on mainnet, testnet or devnet. Until it is activated, Agave runs every check and reports failures to its metrics, but rejects nothing it did not reject before. - * * Same verdict, whatever arrived Our model follows that code with the same library, version and features (reed-solomon-erasure 6.0.0 with simd-accel), 987-byte shards and Agave’s Merkle construction. We built four malformed batches, the first or last coding shred flipped by one bit after encoding, one data shred flipped, and all coding shreds computed from different data, and rebuilt each from 200,000 random subsets of every size from 32 to 64 shreds: Honest batch, 200,000 subsets — all pass Malformed batches, 800,000 subsets — 0 pass …failed as Merkle mismatch — 629,095 …failed as wrong parity — 170,905 The reason is simple once written down. The rebuild produces one Reed–Solomon codeword from the shreds it used. For the batch to pass, that codeword must equal the signed shreds at every position the validator did not receive (or the Merkle root differs) and at every coding position it did receive (or the parity check fails). The positions it used to rebuild match by construction. So passing requires the codeword to match all 64 signed shreds, which only happens if the batch was a valid codeword to begin with. In the packet-loss simulation every validator that reached 32 shreds flagged every malformed batch, at every loss rate from 0 to 45%, and passed the honest one. That held with one shred per insert, and again with inserts of a random 1 to 16 shreds, where about half the failures of the last-coding-shred batch came from the parity comparison rather than the Merkle root. The only validators without a verdict were those that never collected 32 shreds through Turbine, 16 to 19% of them at 45% loss and none at 20% or less; they run the same check when repair brings the batch to 32. - * * What rebuilding every batch costs Less than it sounds. The old code already rebuilt a batch whenever a data shred was missing when it reached 32 shreds, and that is nearly always. The leader sends a batch’s data and coding shreds together, and Turbine routes each shred down its own tree. If they arrive in random order, the first 32 are exactly the 32 data shreds with probability 1 in C(64, 32), about 5.5 × 10⁻¹⁹, which fits the pull request’s estimate that the old code skipped recovery less than once in a trillion batches. What is new is re-encoding the received coding shreds when an insert pushes a batch past 32 at once, and rebuilding batches made only of data shreds. Timed on our model, per batch: Old recovery, 16 data + 16 coding — 299 µs New rebuild, same 32 shreds — 299 µs New rebuild, 24 data + 16 coding — 325 µs New rebuild, 32 data and no coding — 299 µs New rebuild, all 64 received — 305 µs Most of that is hashing the 64 leaves of the Merkle tree, about 214 µs here, which both versions do; encoding 32 coding shreds takes about 83 µs. For the same 32 shreds the new path costs exactly what the old one did, and re-encoding the received coding shreds adds about 9% when more than 32 are present. These are timings of our model on an Apple M1 Pro; absolute numbers depend on the hardware, SHA-256 speed above all. - * * Two Turbine changes around it Rebuilt coding shreds are now retransmitted. A validator forwards rebuilt shreds to its children in Turbine, so one dropped packet upstream doesn’t starve everyone below. When an insert brings only data shreds, the rebuild produces coding shreds the validator never received, and the first version of always-recover, keeping Agave’s old output, threw them away. #16622 forwards them as well, which its description notes Firedancer already did. Leaders no longer rebroadcast. When a leader’s own slot had not been seen by enough stake, Agave’s replay stage asked the broadcast stage to send every data and coding shred of that slot again, after 5 seconds and then at doubling intervals up to 320 seconds. On 5 November #16595 removed Tower consensus from Agave’s replay stage as part of its Alpenglow cleanup, and the request went with it. Hours later #16713 deleted the broadcast worker that served those requests. The issue behind it had called the mechanism redundant if repair works. A validator missing shreds now has two ways to get them: rebuild them from the batch, or ask a peer through repair. - * * What this does not show We ran a model of Agave’s rebuild, not Agave: shred headers, signatures, the per-shred checks and the batch header checks are not in it, and a malformed batch here differs from an honest one only in its erasure coding. Arrival order and loss are random and independent, which real Turbine is not, and inserts are one shred or a random 1 to 16. We had no cluster, so nothing here was measured on a network; the timings are from an Apple M1 Pro, not validator hardware. Firedancer’s implementation of the same rules was out of scope. - * * Shred Recovery: Questions People Actually Ask What is a Solana erasure batch? A group of 64 shreds: 32 data shreds carrying the block and 32 coding shreds that are their Reed–Solomon parity, so any 32 of the 64 rebuild the rest. All 64 are leaves of one Merkle tree, and the leader signs its root. Agave and SIMD-0504 also call it an FEC set. What does SIMD-0504 change about shred validation? It replaces the checks validators inherited from Agave’s code with a written list: sixteen checks on each shred and eight on each erasure batch as a whole. A batch that fails them makes the block invalid: under TowerBFT a validator must not vote for it, under Alpenglow it must vote skip. The checks apply whether a shred was received or rebuilt, so a validator has to check the coding shreds even when all 32 data shreds arrived. What is “always recover” in Agave? Agave rebuilds every erasure batch once, in the batch of incoming shreds that first brings it to 32 shreds, even when no data shred is missing. It re-encodes the coding shreds from the data, compares them with the coding shreds it received, and rebuilds the Merkle tree from all 64 leaves to compare with the signed root. With the SIMD-0504 feature active, a failure marks the slot dead unless it was already full. Is SIMD-0504 active on Solana mainnet? No. Agave’s gate, stricter_shred_validation, was merged on 30 October 2026 and had no feature account on mainnet, testnet or devnet on 6 November. Until it is activated, Agave runs the checks and reports failures to metrics without rejecting anything new. Does a Solana leader still rebroadcast slots that did not propagate? Not in Agave’s main branch. The replay code that asked a leader to resend an unpropagated slot was removed on 5 November 2026 along with Tower consensus in replay, and the broadcast worker that served those requests was deleted the same day. Missing shreds are recovered from erasure coding or fetched through repair. - * * The one-line version A leader can sign an erasure batch whose shreds each verify but whose coding shreds aren’t the parity of its data; SIMD-0504 makes that batch invalid, and Agave now rebuilds every batch once it has 32 shreds, so every validator rejects it the same way, whichever shreds reached it. Sources: SIMD-0504 at commit 33c75cff; Agave #16194, #16378, #16639, #16622, #16595 and #16713, read at commit 9eacc06d; the old recovery path at dd6f564f. The model, simulation and timings are our own.

  2. When a Solana Feature Activates but Its Program Migration Fails Originally published on xroot.dev. On Solana, a feature gate activating and the change it carries actually happening are two separate events. For most gates the difference never shows. For a gate that replaces a core program’s bytes, it can: the replacement runs once, at the epoch boundary, and if it fails, the gate stays active anyway. SIMD-0690, merged on 26 October, adds a deliberate way for that replacement to fail: the runtime hashes the new program bytes and refuses to deploy them unless they match a hash shipped inside the validator. Agave implemented it on 29 October. On 2 November it queued the first migration that relies on it, p-ATA, a rewrite of the Associated Token Account program, and Firedancer followed on 3 November. We pulled the real p-ATA buffer from mainnet, hashed it the way both clients do, and replayed every way the activation can go through the loader Agave uses. The check works. It also depends on something no code enforces: the two clients agree only if the gates activate in the right order. - * * Activated is not the same as replaced In the first bank of each epoch, Agave works out which feature gates have just activated, writes the activation slot into each gate’s account, and then runs the migrations tied to those gates. The p-ATA one is a single call, and its failure path is a log line: if new_feature_activations.contains(&feature_set::replace_ata_with_p_ata::id()) && let Err(e) = self.upgrade_loader_v2_program_with_loader_v3_program( &feature_set::replace_ata_with_p_ata::SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, &feature_set::replace_ata_with_p_ata::PROGRAM_BUFFER, feature_set::replace_ata_with_p_ata::BUILD_HASH, true, "replace_ata_with_p_ata", ) { warn!("Failed to replace SPL Associated Token Account with p-ATA buffer '{}': {e}", ...); } Every check runs before anything is written, so a failure changes nothing except the gate. The gate is now active, so it is never a new activation again and the call never repeats. The proposal says so in one line: on a mismatch, “the feature is still marked as activated, but the program data remains unchanged.” A retry means a new gate in a new release. That leaves four states a migration can be in: Gate account does not exist — not submitted Gate exists, no activation slot — pending, next epoch Active, program under Loader v3 — migrated Active, program under its old loader — activated, migration failed - * * What the hash check adds A migration copies a Loader v3 buffer account, uploaded ahead of time, into the target program’s address. Before SIMD-0690, the runtime checked that the buffer existed, was owned by the upgradeable loader and deserialized as a buffer, then deployed whatever it held. Agave’s implementation adds one step between those checks and the deploy: let buffer = Self::new_checked(bank, buffer_address)?; let data = buffer.buffer_account.data(); let elf = &data[UpgradeableLoaderState::size_of_buffer_metadata()..]; let end_offset = elf.iter().rposition(|&x| x != 0).map_or(0, |i| i + 1); let hash = solana_sha256_hasher::hash(&elf[..end_offset]); if hash != expected_hash { return Err(CoreBpfMigrationError::BuildHashMismatch(hash, expected_hash)); } Skip the 37-byte buffer header, drop trailing zero bytes, take SHA-256, compare. Only then does the deploy step load and verify the ELF in the runtime’s program environment. That step deliberately still accepts SBPF v0 programs “for core program migrations”, which matters here because the p-ATA ELF is v0. What came before is the reason for the change. When p-token replaced SPL Token in place on 13 May 2026, the function that did it took a program address, a buffer address, a flag and a log name. There was no hash. A Loader v3 buffer always has a write authority (the loader refuses to remove it), so until the activation slot, any change that authority made to the buffer, as long as it still loaded and verified, would have become SPL Token. Our p-token explainer covers that migration. Agave’s SIMD-0690 change also turns the expected hash from an optional parameter into a required one, so every future migration must name its bytes, while the check itself waits for its own gate, core_bpf_hash_validation. - * * The real buffer, hashed SIMD-0567 merged on 1 November at 11:02 UTC. Twenty-seven minutes later, the p-ATA buffer appeared on mainnet: 37 transactions in slots 464,075,848 to 464,075,854, between 11:30:19 and 11:30:21 UTC. No transaction has touched it since. We read its bytes and hashed them both ways: Account size — 35,869 bytes Loader v3 buffer header — 37 bytes, authority set ELF — 35,832 bytes, SBPF v0 Trailing zero bytes — 15 SHA-256, zeros removed — 4e008052⁠…⁠7f7d9e Agave, Firedancer and SIMD-0567 expect — 4e008052⁠…⁠7f7d9e SHA-256 of the whole ELF — 8dc8d134⁠…⁠3605a4 The buffer matches. The 15 zero bytes are worth a second look, because they are not padding. The buffer holds exactly one ELF file, and the file itself ends in zeros: its last section header has an alignment of 1 and an entry size of 0. Hash the ELF as a file and you get a different value. The expected hash is defined over the stripped bytes, the way solana-verify computes it, so solana-verify get-buffer-hash reproduces it straight from the chain. The header also shows the buffer’s authority is still set, to 8pEc9HbS…7pkzf. Until the gate activates, whoever holds that key can rewrite or close the buffer. Before SIMD-0690 a rewrite would have been deployed. Now it can only make the activation fail. - * * Seven ways the activation can go To see the success and failure paths without a cluster, we built a harness around the real buffer bytes. It runs Agave’s check copied verbatim, Firedancer’s check ported from C, and the deploy step through solana-sbpf 0.26.0, the loader version Agave pinned at the commit we read, configured the way Agave’s migration path configures it. With the SIMD-0690 gate active, both clients give the same answer for every case: Buffer as it is on mainnet — migrated Plus 4,096 zero bytes — migrated, same hash Minus its last 15 zero bytes — hash passes, loader rejects One .rodata letter changed — hash mismatch Instruction 17 immediate, 83 → 84 — hash mismatch First opcode set to 0xff — hash mismatch Buffer closed before activation — account not found Every failure leaves the gate active and ATA exactly as it was. Two of the rows are worth a closer look. The hash pins the content but not the length. A buffer missing its last 15 bytes hashes identically, because those bytes are zeros. It is still a broken ELF: its section header table now runs past the end of the file, and the loader stops with “Offset or value is out of bounds.” The check and the loader cover each other. Neither alone would catch both a substituted program and a truncated one. The authority problem has changed shape rather than disappeared. A changed letter in a string constant and a changed constant in an instruction both pass the loader and the verifier. Only the hash notices them. The buffer’s authority can no longer decide what ATA becomes, but closing or rewriting the buffer before activation still decides that ATA does not change, and a retry then needs a new gate. - * * When the order slips, the clients split The two clients compute the same hash, but they decide differently whether to compute it. Agave hashes only if core_bpf_hash_validation is already active and otherwise skips the check. Firedancer always passes the p-ATA hash and checks whenever one is passed. Its feature map says why: it “assumes this feature gate is activated for all migrations”, because the SIMD-0690 gate will activate first. The same harness, with the SIMD-0690 gate inactive when p-ATA activates: Buffer as it is on mainnet — both migrate One .rodata letter changed — Agave deploys, Firedancer refuses Instruction 17 immediate, 83 → 84 — Agave deploys, Firedancer refuses First opcode set to 0xff — both refuse, for different reasons In the highlighted rows the clients finish the epoch’s first slot with different bytes at the ATA address, so they compute different bank hashes. Validators executing with Firedancer’s runtime would disagree with validators executing with Agave’s about the same block. Frankendancer, the hybrid, executes with Agave’s runtime, so the line falls between runtimes rather than client brands. Which software each validator runs is visible in gossip and in block footers. None of this is a bug today. The buffer on mainnet matches, so the order makes no difference to the bytes that would be deployed. SIMD-0567 names SIMD-0690 as a dependency, and both clients document the order. Activating both in the same epoch is also safe: Agave installs the new feature set before it runs any migration, so the check is already on. But the order is a scheduling commitment, not a check in code. Anza’s activation guidelines fix a priority order and say exceptions will occur. The thing to watch is simple: gate FCsQnd4V…zp1u must be active no later than gate pata6uFa…ksx3. Neither gate account had been created on mainnet when this was written. - * * Telling the states apart A failed migration leaves no transaction and no event on chain. The only direct record is a warning in each validator’s log. It can still be detected from three accounts, read in one call: const FEATURE_OWNER = 'Feature111111111111111111111111111111111111'; const LOADER_V3 = 'BPFLoaderUpgradeab1e11111111111111111111111'; async function migrationState(rpc, gate, program, buffer) { const res = await fetch(rpc, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getMultipleAccounts', params: [[gate, program, buffer], { encoding: 'base64' }] }), }); const [g, p, b] = (await res.json()).result.value; // Feature account: owned by the Feature program, byte 0 = Some(activated_at) const data = g?.owner === FEATURE_OWNER ? Buffer.from(g.data[0], 'base64') : null; if (!data) return 'gate not submitted'; if (data[0] !== 1) return 'gate submitted, pending'; const slot = data.readBigUInt64LE(1); if (p.owner === LOADER_V3) return `activated at ${slot}: migrated`; return `activated at ${slot}: MIGRATION FAILED (buffer ${b ? 'still there' : 'gone'})`; } // p-token: activated at 419472000: migrated // p-ATA: gate not submitted The two comments are the script’s output against mainnet, for p-token and p-ATA. Checking the owner of the gate account matters: a gate’s address can hold lamports in an ordinary system account before anyone submits it, and reading those as feature data would report nonsense. Our upgrades tracker follows which protocol upgrades are live. When p-ATA does migrate, the program account at ATokenGP…8knL changes owner from BPFLoader2111… to BPFLoaderUpgradeab1e…, gains a program-data account and has no upgrade authority. SIMD-0567 adds that ATA cannot be invoked during the migration slot, because Loader v3 programs take effect one slot after deployment. Tools that decide whether a program is upgradeable by its loader will see the change. - * * Two smaller things in the code The log reads backwards. The mismatch error is built as BuildHashMismatch(hash, expected_hash), with the buffer’s hash first, but its message labels the first value “Expected”. For our altered-letter buffer, the warning prints: Invalid build hash. Expected: 7c10bb1d…a3b147e730, Got: 4e008052…674e7f7d9e The value after “Expected” is what the buffer holds, and the value after “Got” is what the validator expected. The labels have been this way since the error was introduced in April 2025. Anyone diagnosing a failed migration from logs should swap them. A panic fixed in passing. The 2025 version of this check searched for the last non-zero byte across the whole account, header included. When the program bytes were all zero, as in a buffer created but never written, that search could land inside the 37-byte header and produce a slice that ends before it starts. Our harness reproduces the panic for a header with no authority, and for an authority whose last byte is zero. On a real cluster it would have stopped every validator at the activation slot. The SIMD-0690 commit fixed it under the line “strip trailing zeros from ELF bytes only”. It was never reachable on mainnet: the only migration that ever passed a hash before then was the slashing program’s, whose gate was never created on mainnet, and Agave removed it in June 2026. Firedancer’s version never had the problem. - * * What this does not show The harness replays the checks and the loader on real bytes. It does not run a cluster through an epoch boundary. The deploy column uses Agave’s loader for both clients; Firedancer has its own loader, which we did not run. The altered buffers are our edits, not anything seen on chain. We checked the on-chain buffer against the published hash but did not rebuild p-ATA from source with solana-verify. We read mainnet only, not testnet or devnet. - * * Program Migrations: Questions People Actually Ask Can a Solana feature gate be active while the program it was meant to replace is unchanged? Yes. A core program migration runs once, inside the first bank of the epoch in which its feature gate activates. If any check fails, the runtime logs a warning and moves on: the feature account records its activation slot and the target program keeps its old bytes. Nothing retries, because the gate is never newly activated again. A second attempt needs a new feature gate in a new validator release. What exactly does SIMD-0690 hash? The program bytes in the source buffer account: everything after the 37-byte Loader v3 buffer header, with trailing zero bytes removed, hashed with SHA-256. The result must equal a hash compiled into the validator next to the feature gate. solana-verify computes executable hashes the same way, so solana-verify get-buffer-hash reproduces the value from the chain. Is the p-ATA buffer on mainnet the one Agave expects? Yes. The buffer at pataAAkF439TTjwRqmxRkTTEfZemRdoriXusez4PQ6S hashes to 4e008052…7f7d9e, the value in Agave’s replace_ata_with_p_ata gate, in Firedancer’s constant and in the SIMD-0567 text. It was created and written in 37 transactions on 1 November 2026 and has not been written since. Do Agave and Firedancer run the hash check the same way? They compute the same hash but differ on when they check it. Agave checks only once the SIMD-0690 gate, core_bpf_hash_validation, is active. Firedancer always checks the p-ATA hash and records that it assumes the SIMD-0690 gate activates first. If p-ATA activated before SIMD-0690 with an altered buffer, Agave would deploy the altered bytes, Firedancer would refuse them, and the two clients would compute different bank hashes. How do I check whether a migration actually happened? Read three accounts. The feature account is owned by the Feature program and holds an activation slot once active. The target program is owned by BPFLoaderUpgradeab1e… after a successful migration. The source buffer is consumed on success. An active gate with the program still under its old loader means the migration failed. - * * The one-line version SIMD-0690 makes a core program migration deploy the reviewed bytes or nothing. A failure leaves the gate active and the program unchanged, and Agave and Firedancer agree on which happened only if the hash-check gate activates first. The last in-place swap is in SPL Token was replaced in place, the prefunded-account rule p-ATA builds on is in SIMD-0312’s CreateAccountAllowPrefund, and how to tell which client built a block is in reading the block footer. Sources: SIMD-0690 at commit eac20f29; SIMD-0567 at 93cdf03a; Agave #16077 and #16524 (source read at 9260869d); Firedancer #12680 (37c65819); the buffer and gate accounts read from mainnet with getAccountInfo, getMultipleAccounts and getSignaturesForAddress. Scenario results are from our own harness.

  3. One Lamport Can Break Consensus: Solana’s Integer Inflation Originally published on xroot.dev. Once an epoch, every Solana validator computes how many lamports of inflation to mint, adds them to the chain’s capitalization, and folds the result into its bank hash. Today that calculation runs in floating point, with a powf in the middle of it. SIMD-0607, written by engineers from both Anza and Firedancer, replaces every floating-point step with integer arithmetic specified down to the order of its divisions. Agave merged the implementation on 30 October behind a feature gate. We copied the specification’s Rust into a test harness to check its claims and to measure the problem it fixes. Two mathematically identical float formulas for the same reward disagreed by one lamport in 62 of 60,000 epochs. The integer path cannot disagree with itself. - * * Why one lamport is a consensus failure Capitalization is part of the state every validator hashes. If two clients compute the same epoch’s reward and land one lamport apart, their capitalizations differ, their bank hashes differ, and the network has a disagreement no vote can fix. The proposal’s motivation is exactly that: these values “must be computed identically by all validator clients, without relying on IEEE-754 behavior or platform math-library functions such as powf.” Basic float operations are deterministic. Exponentiation is not specified to the last bit, and the inflation rate is 0.08 × (1 − taper)^year. To see how much that matters, we computed the legacy epoch reward two ways that are equal on paper, (1 − taper).powf(year) and exp(year × ln(1 − taper)), for 6,000 epochs in each of five slot-time regimes and both tapers: Capitalization 10¹⁸ lamports — 62 of 60,000 rewards differ Capitalization 6.2 × 10¹⁷ lamports — 47 of 60,000 rewards differ Size of every difference — 1 lamport About one epoch in a thousand, the truncation that turns a float reward into lamports lands on the other side of an integer. That is not a claim that Agave and Firedancer compute different rewards today. It is the reason neither should depend on how a math library rounds the last bit. - * * The integer schedule Rates are integers over RATE_SCALE = 10^15, so 8% is 80_000_000_000_000. Decay factors are integers over 2^64, a finer scale because a factor very close to one is multiplied by itself across hundreds of millions of slots. The per-slot factor is derived, not tabulated: a 32-term series for the logarithm of the taper and a cubic approximation of the exponential, then a binary exponentiation whose order and flooring are fixed: fn decay_per_slot(ns: u128, taper_num: u32, taper_den: u32) -> Option<u128> { // -ln(1 - taper) = ln(taper_den / (taper_den - taper_num)), scaled by 2^64 let annual_exponent = approximate_logarithm(taper_den, taper_den.checked_sub(taper_num)?)?; let slot_exponent = annual_exponent.checked_mul(ns)? / YEAR_NS; approximate_exponential(slot_exponent) // 2^64 * exp(-x), cubic } fn pow_decay_floor(mut base: u128, mut n: u64) -> u128 { let mut result = DECAY_SCALE; while n > 0 { if n & 1 != 0 { result = mul_decay_floor(result, base); } n >>= 1; if n > 0 { base = mul_decay_floor(base, base); } } result } // rate = max(TERMINAL_RATE, floor(anchor_rate * decay / 2^64)) // annual = floor(capitalization * rate / 10^15) // reward = floor(annual * slots_in_epoch * ns_per_slot / YEAR_NS) Every step floors, every multiplication is checked, and an overflow is an error the validator must turn into a panic. The specification forbids wrapping, saturating or substituting a value, and it forbids falling back to floating point. - * * Its numbers, reproduced to the lamport The proposal publishes how far the integer schedule drifts from the floating-point one: the largest difference in a single epoch’s reward over 6,000 epochs, at a capitalization of 10¹⁸ lamports, across the 400, 350, 300, 250 and 200 ms slot regimes. Running its functions verbatim against its stated float baseline: 15% taper, largest difference — 5,923 lamports (spec: 5,923) 30% taper, largest difference — 1,061 lamports (spec: 1,061) Where the 15% worst case falls — 350 ms, epoch 1,282 Where the 30% worst case falls — 400 ms, epoch 509 32-term logarithm vs 200 terms — identical, both tapers Both drift figures match exactly. So does the claim that 32 terms of the logarithm series are enough: for the 15% and 30% tapers, the 32-term sum and a 200-term sum are the same integer, to the last of 2⁶⁴ units. The drift is the price of the change, at most a few thousand lamports of an epoch’s reward. A relative difference below 7 × 10⁻¹¹ buys a reward every client computes identically. - * * Overflow, and why it panics With 128-bit intermediates, the reward arithmetic stays valid up to a capitalization of about 18.4 trillion SOL, a thousand times beyond the 18.4 billion SOL a 64-bit lamport count can hold at all. The checks that can fail in practice are the invalid inputs: a zero slot duration, or a taper of 100% or more, return an error before any arithmetic. The panic rule is deliberate. A validator that cannot compute the reward exactly as specified is safer stopped than running on a number the rest of the cluster did not compute. That is the same reasoning as the proposal itself, applied to its own failure path. - * * The activation order with SIMD-0550 SIMD-0550 doubles the yearly disinflation from 15% to 30%. Its original design kept issuance continuous by recomputing the stored initial rate at activation, initial = anchor / (1 − 0.30)^year, with powf. Our earlier post on SIMD-0550 described that design. An amendment merged on 29 October replaced it: SIMD-0607 must activate first or at the same boundary, the anchor is computed with the integer 15% schedule including its terminal clamp, and the 30% integer taper applies from the activation slot. Agave rekeyed the SIMD-0550 gate in the 4.4.0 release. Anchor rate, activation at year 6 (illustrative) — 3.0172% Rate zero slots after activation — 3.0172% Rate one year later — 2.1120% Naive governor formula, 0.08 × 0.70⁶ — 1.5% (terminal floor) The anchor works: at zero elapsed slots the decay factor is exactly 2⁶⁴, so the rate does not move. The trap is for anyone reading the stored governor. After activation it reports initial = 0.08 and taper = 0.30, and the textbook formula built from those two numbers, which measures decay from genesis, lands on the 1.5% floor while the chain is minting at about 3%. Forward curves should start from getInflationRate, not from getInflationGovernor. - * * Where it stands Agave’s implementation, #16355, merged on 30 October behind the remove_runtime_float_ops gate. Neither that gate nor SIMD-0550’s has been activated on mainnet. When SIMD-0607 does activate at the boundary into epoch E+1, rewards earned in epoch E are still paid with the legacy calculation, and the integer path starts with rewards earned in E+1. - * * What this does not show We ran the specification’s code, not Agave’s implementation of it, and compared against the float baseline the specification states rather than against a running validator. The two float paths in section 01 are a test of sensitivity, not a measurement of any client. The year-6 activation in section 05 is illustrative, at constant 400 ms slots; the real anchor depends on the slot-time history up to the activation slot. - * * Integer Inflation: Questions People Actually Ask Why does Solana want to remove floating point from inflation? Inflation rewards are added to capitalization, which is part of the bank hash. If two validator clients compute an epoch’s reward one lamport apart, their bank hashes differ. Floating-point exponentiation depends on the math library, and mathematically identical float formulas can round to different lamport amounts. SIMD-0607 specifies the whole calculation in integers so every client gets the same number. How large is the difference between the integer and floating-point schedules? At most 5,923 lamports per epoch for the 15% taper and 1,061 for the 30% taper, over 6,000 epochs and five slot-time regimes at a capitalization of 10^18 lamports, a relative difference below 7.03 × 10^-11. We reproduced both figures exactly from the specification’s code. What does SIMD-0607 have to do with SIMD-0550? SIMD-0550 doubles the disinflation rate from 15% to 30% a year. Since an amendment merged on 29 October 2026, it must activate after or together with SIMD-0607: the anchor rate at activation is computed with the integer 15% schedule, and the 30% integer taper applies from there. The stored inflation governor keeps initial 0.08 and switches taper to 0.30. Can I compute Solana’s inflation rate from getInflationGovernor? Not after SIMD-0550 activates. The governor will report initial 0.08 and taper 0.30, but the rate decays from the activation anchor, not from genesis, so initial × (1 − taper)^years gives the wrong answer. Read getInflationRate, which the node computes. Is SIMD-0607 active on mainnet? Not as of 30 October 2026. Agave merged the implementation that day behind the remove_runtime_float_ops feature gate, which has not been activated on mainnet. - * * The one-line version Floating point lets two correct implementations disagree by a lamport about once in a thousand epochs; SIMD-0607 trades a few thousand lamports of drift per epoch for a reward every client computes identically, and SIMD-0550 now waits for it. The faster disinflation itself is in Solana’s inflation will decay twice as fast, how those rewards reach stake accounts is in when staking rewards arrive, and why two independent clients matter at all is in which client built each block. Sources: SIMD-0607 as amended at commit 2934dbeb; the SIMD-0550 amendment; Agave #16355 (commit 9bcf93e368) and #16499. Measurements are from the specification’s functions run in our own harness.

  4. Solana P-256 Syscalls: Agave and Firedancer, Edge Case by Edge Case Originally published on xroot.dev. On 27 October both of Solana’s independent runtimes merged the same new feature, written separately. Agave added P-256 curve operations to its syscalls at 02:38 UTC; Firedancer added its own sixteen hours later. Different authors, different languages, different arithmetic, and consensus needs them to return the same bytes for every input a program can send, including every input the specification rejects. SIMD-0565 extends Solana’s curve syscalls to P-256, also called secp256r1: the curve behind passkeys, WebAuthn and the secure chips in phones. We wrote our own reference implementation from the curve’s published constants, generated 626 test vectors aimed at the edges of the specification, ran them through the code Agave’s syscalls call, and checked Firedancer’s tests and source for the same cases. We found no case where the two differ. We also built a small program that verifies a P-256 signature with three of the new syscalls and no modular inverse. It costs 22,760 compute units. - * * What SIMD-0565 adds No new syscall names. The proposal adds two curve ids, 7 for little-endian and 0x87 for big-endian, to the four curve syscalls programs already use for curve25519 and BLS12-381: point validation, decompression, group operations (add, subtract, multiply by a scalar) and multiscalar multiplication. Points are 64 bytes, x then y, and the point at infinity is 64 zero bytes. Compressed points are 33 bytes: a 02 or 03 parity prefix, then x. Every call has a fixed price, charged before its inputs are read, whether it succeeds or not: Validate a point — 20 CU Decompress — 610 CU Add or subtract — 520 CU Multiply by a scalar — 9,000 CU Multiscalar, n points — 9,000 + 3,800 × (n − 1) Most points within 1.4M CU — 367 A multiscalar call with more than 512 points fails outright, before anything is charged. The SIMD’s authors set the prices on a private ten-validator cluster with 200 ms slots, so that a block packed with any one of these operations still replays within its slot. - * * Two implementations of one rulebook Agave’s version, #13918, opened in July, calls solana-secp256r1 0.1.0, a pure-Rust P-256 crate Anza published in September. Firedancer’s, #12498, opened on 22 October, is C built on AWS’s s2n-bignum routines where the build has them. What the two share is everything consensus can see: the same feature-gate key, the same seven compute prices, and five test vectors. Firedancer’s syscall tests reuse the expected outputs from Agave’s, in both byte orders. Agreement matters more than either implementation being right by its own tests. If one client returns 0 where the other returns 1, a program that branches on the result writes different account data on each, the two compute different bank hashes for the same block, and the network splits along client lines. The dangerous inputs are the ones nobody thought to try. - * * The edge cases, and what both clients do These are the inputs where a reasonable implementation could choose differently. In every row the two give the same answer: Agave on our vectors through the crate its syscalls call, Firedancer in its own tests or, where its tests don’t reach, in its source. 64 zero bytes — identity, valid x = 0, y = √b, a real point — valid (0, 1) — rejected x or y written as value + p — rejected Prefix other than 02 or 03 — rejected 33 zero bytes, SEC1’s identity — rejected P + P, or P − (−P) — 2P P − P, or k·P + (n − k)·P — identity 0·P, or k times the identity — identity Scalar n, n + 1 or 2²⁵⁶ − 1 — rejected Multiscalar, 0 points — identity Multiscalar, bad last scalar or point — rejected Multiscalar, over 512 points — InvalidLength Any P-256 call before activation — InvalidAttribute “Rejected” means the call returns 1 and writes nothing; the last two rows are errors that abort the transaction. Firedancer’s tests cover the identity, off-curve and out-of-range points, the 04 prefix, both byte orders, P + P, P − P, P + (−P), adding and subtracting the identity, 0·P, k times the identity, n·P, multiscalar calls of 0, 1, 2 and 70 points including a bad scalar or point, running out of compute and the gate being off. The x = 0 point, the value-plus-p encodings, the other prefixes, n + 1, doubling through subtraction and cancellation inside a multiscalar call we confirmed in its source. - * * 626 vectors, and three things to know before you call them Our reference is about 160 lines of Python written from the curve constants in NIST SP 800-186 and the SIMD text, without reading either client’s arithmetic. It reproduces all five shared vectors. From it we generated 313 vectors per byte order: 73 hand-picked cases and 240 random operations. The hand-picked ones include everything in the table above, 64-point multiscalar calls whose two 32-point halves are equal or cancel (Firedancer works in batches of 32 points, so these land on its batch boundary), and calls of 367 and 512 points. Run through solana-secp256r1 0.1.0, called exactly as Agave’s syscalls call it at the merge commit, all 626 match, including all 127 rejections. - x = 0 is on the curve. The identity’s encoding has x = 0, and so does a real point, (0, √b). Both clients treat only the exact all-zero encoding as the identity, so (0, √b) is an ordinary point, decompression of x = 0 succeeds, and (0, 1) is rejected. - Scalars are never reduced. A scalar of n, n + 1 or 2²⁵⁶ − 1 makes the call return 1, even when the point is the identity. A SHA-256 hash is a valid scalar only if it is below n, so reduce it first: any 256-bit value is below 2n, so one comparison and one subtraction are enough. - Little-endian is per coordinate. With curve id 7, x and y are each byte-reversed in place, and a compressed point keeps its prefix byte first with only x reversed, which is not SEC1. The generator written in the other byte order fails validation. - * * The one place the code differs Read side by side, the clients take different paths in one place: an unknown curve id or group operation in validation, group operations or multiscalar multiplication. Firedancer’s syscalls always abort with InvalidAttribute. Agave aborts only when the feature from SIMD-0137 is active, and otherwise returns 1. That feature has been active on mainnet since slot 311,904,000, the first slot of epoch 722, so the result is the same there. Nothing on the P-256 path itself branches this way. - * * A signature check from three syscalls The obvious use is checking passkey signatures inside a program, which today goes through the secp256r1 precompile. ECDSA verification normally needs s⁻¹ mod n, and no syscall computes it. It can be avoided. The signature is valid when s·R equals z·G + r·Q up to sign, where R is the point whose x-coordinate is r, z the message hash and Q the public key. Decompression gives R, one multiplication gives s·R, one two-point multiscalar call gives z·G + r·Q, and comparing x-coordinates ignores the sign: use solana_define_syscall::{ curve_constants::{GROUP_OP_MUL, SECP256R1_BE}, definitions::{sol_curve_decompress, sol_curve_group_op, sol_curve_multiscalar_mul}, }; /// ECDSA check: s·R == ±(z·G + r·Q), where R is the point with x = r. /// Same answer as the textbook check except for x(R) = r + n, which has /// probability about 2^-128 and is not tried here. pub fn verify_p256(q: &[u8; 64], z: &[u8; 32], r: &[u8; 32], s: &[u8; 32]) -> bool { if is_zero(r) || is_zero(s) || *r >= N || *s >= N { return false; } // The syscalls reject scalars >= n instead of reducing them. let z = if *z >= N { sub(z, &N) } else { *z }; let mut c = [0u8; 33]; c[0] = 0x02; // either parity: s·(−R) = −(s·R) has the same x c[1..].copy_from_slice(r); let (mut rp, mut u, mut t) = ([0u8; 64], [0u8; 64], [0u8; 64]); let scalars = [z, *r]; let points = [G, *q]; unsafe { // R: 610 CU if sol_curve_decompress(SECP256R1_BE, c.as_ptr(), rp.as_mut_ptr()) != 0 { return false; } // U = s·R: 9,000 CU if sol_curve_group_op(SECP256R1_BE, GROUP_OP_MUL, s.as_ptr(), rp.as_ptr(), u.as_mut_ptr()) != 0 { return false; } // T = z·G + r·Q: 9,000 + 3,800 CU; returns 1 if Q is not a valid point if sol_curve_multiscalar_mul( SECP256R1_BE, scalars.as_ptr() as *const u8, points.as_ptr() as *const u8, 2, t.as_mut_ptr(), ) != 0 { return false; } } t != [0u8; 64] && u[..32] == t[..32] } // N and G are the group order and generator, big-endian; is_zero and sub are byte helpers. We built it with platform-tools v1.57 and ran it in solana-sbpf 0.26.0, the VM version Agave pinned at the merge, set up as in our CPI measurement, with the three curve syscalls implemented the way Agave implements them on top of the same crate. OpenSSL signed 20 messages, and the program got each signature, the same signature with the other half of S, a hash with one bit flipped, and a different public key. All 80 verdicts match textbook ECDSA: Curve syscalls: 610 + 9,000 + 12,800 — 22,410 CU Memory syscalls — 30 CU Program instructions — 320 CU One signature check — 22,760 CU Precompile, cost-model charge — 4,800 CU Two differences from the precompile. Cost: a check through the syscalls takes about 4.7 times the 4,800 compute units the cost model charges per precompile signature, before the message is hashed. Policy: this check accepts both halves of S, where the precompile rejects the high one, the reason half of raw WebAuthn signatures fail it. A program that wants the precompile’s rule adds s ≤ n/2; one that doesn’t can skip normalising S altogether. The SIMD gives that freedom as the reason for curve operations instead of a single verify syscall, which would have had to fix one malleability rule for everyone. - * * Where it stands Both pull requests introduce the gate secp256r1_syscall_enabled, key SeRjtX7Abiq5hQYbzdCny266oveDseHZh9tnrs2cdVC, so the syscalls were off on every cluster when they merged, and a program calling them gets InvalidAttribute until the feature is activated. The SIMD itself was still marked Draft, with its compute prices added a week earlier. The program side is ready: solana-define-syscall 5.2.0, published in July, already declares the curve ids and all four syscalls, and our program imports them from there. - * * What this does not show We did not build or run Firedancer. Its side rests on reading its source and on its own CI run, which passed test_secp256r1 and test_vm_syscall_curve on the pull request’s final commit, whose P-256 files are identical to the merged ones. On Agave’s side we ran the crate its syscalls call, not the syscalls: memory translation, compute charging and the gate we read rather than executed. Vectors find disagreements; they cannot prove there are none. The compute prices are the SIMD’s calibration, not our measurement, and 22,760 is one program built with one toolchain. - * * P-256 Syscalls: Questions People Actually Ask What does SIMD-0565 add to Solana? It extends the four existing curve syscalls (point validation, decompression, group operations and multiscalar multiplication) to P-256, also called secp256r1, with curve id 7 for little-endian and 0x87 for big-endian encoding. Points are 64 bytes, x then y, with 64 zero bytes for the point at infinity. Agave and Firedancer both merged an implementation on 27 October 2026, behind a new feature gate. How much does it cost to verify a P-256 signature with the new syscalls? Our test program verified an ECDSA P-256 signature for 22,760 compute units: 22,410 for one decompression, one scalar multiplication and one two-point multiscalar multiplication, 30 for memory syscalls and about 320 program instructions. Hashing the message is extra. The secp256r1 precompile is charged 4,800 compute units per signature in the cost model. Do the secp256r1 syscalls reduce scalars modulo the group order? No. A scalar of n or more makes the call return 1 in both Agave and Firedancer, including n + 1 and 2^256 − 1. A program that uses a 256-bit hash as a scalar must reduce it first; since any 256-bit value is below 2n, one comparison and one subtraction are enough. How is the point at infinity encoded for Solana’s P-256 syscalls? As 64 zero bytes. Only that exact encoding is the identity: (0, √b), which also has x = 0, is an ordinary point on the curve, and (0, 1) is rejected. Decompression cannot produce the identity, and 33 zero bytes are rejected. Are the P-256 syscalls active on mainnet? Not when they were merged. Both pull requests introduced the feature gate secp256r1_syscall_enabled, so it was inactive on every cluster, and every P-256 call fails with InvalidAttribute until it is activated. - * * The one-line version Agave and Firedancer implemented SIMD-0565’s P-256 syscalls independently and agree on every edge case we could find, from the x = 0 point to unreduced scalars; with them, a program verifies a P-256 signature for 22,760 compute units and sets its own rule for S. Sources: SIMD-0565 at commit f13f5d65; Agave #13918, merged as cf58f070; Firedancer #12498, merged as 8e7388db; solana-secp256r1 0.1.0, solana-define-syscall 5.2.0 and solana-sbpf 0.26.0. The reference, vectors, harnesses and test program are our own.

  5. Solana Geo Leader Schedule: Location Pays, Splitting Doesn’t Originally published on xroot.dev. Under Alpenglow’s fast leader handover, the gap between two leaders is mostly the time a block takes to cross from one to the next. Today’s schedule picks leaders at random, so that crossing is often an ocean. SIMD-0675 proposes to reorder the schedule so that leaders near each other go one after another. In review, Dean Little raised the obvious worry: that the reordering “appears to incentivize sybils”, letting validators use geography, “or even just a self-attestation of geography”, to line up their leader rotations “with better-than-random odds”. Two days later, the author of the proposal’s parameter analysis answered that splitting stake barely moves the odds, 7.146% to 7.147% in one example, because “the majority of the ‘upside’ for extractors (compared to random) is already given by the schedule design.” We rebuilt the reordering in Rust from the specification to check both statements. The reviewer is right about splitting: it changes nothing, not even one window. But the second half of the sentence is the finding. The upside the schedule hands out depends on where an operator’s stake sits, and an honest operator in the right region gets back-to-back windows five to seven times as often as today, without splitting or lying about anything. - * * Bins, radii and runs The base schedule is computed exactly as today, one stake-weighted random leader per four-slot window. SIMD-0675 then walks it in order and drops each leader into a bin: - Radius. Every validator has a radius, the smallest distance around it that holds STAKE_FLOOR (10%) of all stake. Dense places get small radii, isolated ones large radii. Locations are self-reported, through SIMD-0674. - Bins. A leader joins a random open bin whose anchor is within the larger of the two radii, or opens a new bin if none is. - Runs. When a bin holds RUN_LENGTH leaders, it is shuffled and written out as a block of consecutive windows. The proposal says 3; reviewers have argued for 7. Every validator keeps exactly the windows it would have had. Only their order changes. With 200 ms slots, a run of three windows is 2.4 seconds of block production from one area of the map. for &l in base { let near: Vec<usize> = (0..bins.len()) .filter(|&i| { let a = bins[i].anchor; dist2(&vs[l], &vs[a]) <= r[a].max(r[l]) }) .collect(); let bi = if near.is_empty() { bins.push(Bin { anchor: l, members: vec![l] }); bins.len() - 1 } else { let i = near[rng.roll(near.len())]; // one roll per join bins[i].members.push(l); i }; if bins[bi].members.len() == run_length { let mut b = bins.remove(bi); rng.shuffle(&mut b.members); // Fisher-Yates out.extend(b.members); } } // end of epoch: shuffle the open bins, shuffle each one, append - * * The model, and a check against the proposal Our implementation follows the specification’s pseudocode line for line: the radius, the nearness test, one random draw per join, Fisher-Yates within a run and a shuffled flush at the end of the epoch. Two simplifications are deliberate. The random generator is SplitMix64 rather than the specified ChaCha20, which changes which schedule comes out but not its statistics. And the network is stylized: about 800 validators with lognormal stake across 19 metros. Asia holds 10.4% of stake, the figure in the parameter analysis published on solana.com; the European and North American split and the metro mix are our assumptions. Latency between leaders uses the analysis’ estimate, 1.5 times the great-circle distance at 200 km per millisecond. Each scenario is 108,000 windows, an epoch, averaged over five seeds. Mean handover, random schedule (SIMD’s figure) — 37.3 ms (36.2) RUN_LENGTH 3 — 18.2 ms (17.0) RUN_LENGTH 7 — 11.6 ms (10.8) The proposal’s simulation, on the real stake distribution of epoch 1038 and measured round-trip times, is in brackets. Our stylized network lands within about a millisecond at every run length, which is close enough to trust it for the question it was built for: who gets consecutive windows. - * * Splitting stake changes nothing We gave one operator 3% of stake as one validator in Frankfurt, then as six validators of 0.5% each in the same place. The two schedules were identical, window for window, at every run length. That is not a statistical tie, it is structural. The base schedule gives the six the same windows the one would have had. The radius of a location is computed from all the stake there, so it is the same. And a bin only ever asks about locations and radii, never identities. Sybils at one address are invisible to the reorder, which is the reviewer’s 7.146% to 7.147% taken to its limit. Splitting across regions is worse than useless for an extractor. The same 3% spread over Frankfurt, Ashburn and Singapore led two windows in a row 4.2% of the time at RUN_LENGTH 3, against 7.6% for the operator that stayed in one place. - * * Location changes a lot Consecutive windows under one operator are the precondition for multi-block MEV: two windows are eight slots in which one party orders every transaction. We measured how often an operator’s window is followed by another of its own, under today’s random schedule and under the reorder. Columns are random, then RUN_LENGTH 3, then 7. 1% operator, Frankfurt — 1.06% → 2.56% → 3.31% 1% operator, Tokyo — 1.06% → 5.31% → 7.66% 3% operator, Frankfurt — 3.15% → 7.56% → 9.22% 3% operator, Tokyo — 3.15% → 15.10% → 19.10% 3% split over three regions — 3.15% → 4.18% → 4.90% Under random, the chance is the operator’s stake share, wherever it sits. Under the reorder, a run is filled from nearby stake, so the chance tracks the operator’s share of its region instead. Three percent of all stake is a modest slice of Europe and a large slice of Asia, the smallest region that still forms bins of its own at a 10% floor. The same operator in Tokyo gets back-to-back windows 4.8 times as often as today at RUN_LENGTH 3, and 6.1 times at 7, twice what it would get in Frankfurt. Nobody lied and nobody split stake. This is the schedule working as designed, and it is the reviewer’s point stated with a location attached: the upside comes from the design, and the design pays most to the largest operator in the smallest self-contained region. The solana.com analysis already notes that Asia sits just above the floor, at 10.4%, “with very little margin”. Our numbers say the margin is also where consecutive control is cheapest. - * * And lying about it The attack the proposal’s authors model is a validator that declares an isolated location, so that no one else joins its bins. The solana.com analysis uses the most isolated point it could find, in the South Pacific, and reports that at RUN_LENGTH 7 lying is worth 0.28 extra consecutive windows to a validator with 4.07% of stake, the largest today, and 1.69 to one with 10%. 4.07% operator, back-to-back, truthful in Frankfurt — 4.23% → 10.13% → 12.15% 4.07% operator, back-to-back, declares South Pacific — 4.23% → 32.34% → 41.59% 10% operator, back-to-back, truthful in Frankfurt — 10.05% → 21.72% → 25.98% 10% operator, back-to-back, declares South Pacific — 10.05% → 65.11% → 83.19% Measured the analysis’ way, as the expected run remaining from one of the liar’s windows, our model puts the gain from lying, against the same operator in Frankfurt, at 0.58 windows for the 4.07% validator and 2.43 for the 10% one, both at RUN_LENGTH 7. That is higher than the analysis’ figures, which come from the real map, where a few validators in Argentina and Australia can still reach the isolated point. The direction is the same: lying pays a little below the floor and a lot at it, which is why STAKE_FLOOR is pinned at 10%. The proposal’s own location-fibbing table shows lying rarely improves a validator’s latency. It was never meant to. The reason to lie is the run, not the handover. - * * What it means for the parameters RUN_LENGTH moves every number above. Going from 3 to 7 buys 6.6 milliseconds of mean handover in our model and raises back-to-back control for every operator we measured. The proposal’s authors prefer 3 for exactly this reason; the analysis finds a knee at 7 and calls 3 “the conservative choice”. Reviewers suggested handling Sybils outside the protocol, by raising the validator admission ticket or by detecting misreported locations, and the authors agreed that detection is probably possible. Neither remedy touches the effect in section 04. A ticket prices the number of validators, and the location-driven gain needs only one. Detection catches false locations, and the Tokyo operator’s location is true. If the concern is consecutive control by honest operators, the levers are the run length and the floor, and the floor is already pressed against Asia’s share. - * * What this does not show The network is stylized, so the exact percentages belong to it; the mechanism, that consecutive control tracks local rather than global share, does not depend on the split we chose. Back-to-back windows are a precondition for multi-block MEV, not a measure of its value, which the analysis also stresses. The random generator differs from the specified one. And SIMD-0675 and SIMD-0674 are open proposals, last amended on 22 October, so the constants may still change. - * * Geo-Aware Leader Schedule: Questions People Actually Ask What is Solana’s geo-aware leader schedule? SIMD-0675, an open proposal, keeps today’s stake-weighted random leader schedule and then reorders it: leaders that are geographically close are grouped into runs of RUN_LENGTH consecutive leader windows, so the handover between them is short. Every validator keeps exactly as many windows as before. Does splitting stake into several validators help under SIMD-0675? Not at one location. The reorder sees a validator’s location and stake, not its identity, so six validators in one city with a combined stake behave exactly like one. In our model they produced identical schedules, window for window. Where does the geo-aware schedule give an operator more consecutive windows? Where its share of the local stake is large. A run is filled from nearby stake, so an operator’s chance of leading two windows in a row tracks its share of its region. In our model a 3% operator in Tokyo got back-to-back windows 15% of the time at RUN_LENGTH 3, against 3% under today’s random schedule and 7.6% in Frankfurt. Can a validator lie about its location to get consecutive leader slots? Yes, and the proposal’s parameter analysis measures it: a validator that declares an isolated location ends up alone in its bins. The analysis puts the gain at 0.28 extra consecutive windows for a 4.07% validator at RUN_LENGTH 7, rising sharply once the liar holds STAKE_FLOOR, 10% of stake, which is why STAKE_FLOOR sits at 10%. Is SIMD-0675 live on mainnet? No. As of 26 October 2026 it is an open proposal, as is SIMD-0674, the location registration it depends on. Its parameters, especially RUN_LENGTH, are still being debated between 3 and 7. - * * The one-line version SIMD-0675 halves handover latency and does not reward Sybils, but it turns local stake share into consecutive leader windows, and the smallest region that forms its own bins is where a large operator gains the most. The consensus this schedule feeds is in Alpenglow finality in numbers, the admission ticket reviewers proposed raising is in the 2,000 validator seats, and the stake-weighted QoS that follows the leader is in stake-weighted QoS, with the formula. Sources: SIMD-0675 and SIMD-0674 as amended on 22 October 2026, with their review threads to 26 October; the parameter analysis on solana.com, by Umberto Natale, 14 October 2026. Results are from our Rust model of the reorder, described in section 02.

  6. this could be the most important SIMD in the history of solana: >unlocks neural nets onchain >unlocks cryptography onchain neural nets unlock more advanced models onchain cryptography unlocks privacy onchain both unlock gigacapital onchain solana-improvement-documents#714

    27 likes · 3 reposts · 1,072 views
  7. p-ATA SIMD is merged. Cluster-wide CU reduction in the pipeline. solana-improvement-documents#567

    21 likes · 3 reposts · 6,808 views
  8. Excited about @bw_solana 's proposal to formally specify the @solana protocol: solana-improvement-documents#716 wen Solana validator ASIC

    18 likes · 4 reposts · 4,178 views
  9. every day i wake up and i solana solana-improvement-documents#708

    33 likes · 4 reposts · 6,414 views
  10. IBRL solana-improvement-documents#693

    32 likes · 5 reposts · 3,149 views

Achievements

Awards earned by the repositories solana-foundation owns. Select an award to embed its badge.

No awards yet

Repositories earn awards by reaching the top three in a completed trending day or week.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.