Inside the Block

How ten Solana validators on eight clients order the transactions in their blocks. I measured it on mainnet after Alpenglow, 1 to 3 November 2026, with 1,263 scored pairs of my own transactions.

Still from the video: Y first in 215 of 215 pairs and D in none of 211 on all ten validators, with Maksym on camera Watch the video4:30, YouTube

It depends on the moment

When the leader held both transactions, all ten validators put the one with more reward per unit of cost first in every pair: 215 of 215 in one contrast, 211 of 211 in the other. Once the block was being built, Agave, Jito Labs and Rakurai took them in the order they arrived, and the better one got ahead only 4 times in 119.Firedancer, BAM and Harmonic work in batches of tens of milliseconds, which is most likely why public tests disagree. You can check every pair on chain with getBlock.

The question

When two transactions compete for a place in a Solana block, does money decide, or whoever arrives first? Public tests disagree, so I measured it myself on mainnet. The answer turned out to depend on when they reach the leader.

How a block gets built

Solana runs in slots of about 200 milliseconds. Every slot has one leader, the validator that builds its block, and a leader gets four slots in a row. A lottery weighted by stake sets the leader schedule a day ahead. There's no shared mempool. You send a transaction straight to the leader, or an RPC node sends it for you.

after Alpenglow

Alpenglow went live on mainnet on 21 October 2026. A leader still holds four slots in a row, and transactions reach it the same way as before. Every measurement here comes after the upgrade, and the program refuses to send before it.

Reward and cost

The leader earns the priority fee plus half of the signature fee. That's its reward. Every transaction also has a cost, counted from its compute, signatures, writes and data. Agave, the code most validators are built on, sorts transactions by reward per unit of cost. But is that what validators really do?

the rule in the code

Agave and Jito Labs rank a transaction by reward × 1,000,000 ÷ (cost + 1) (transaction_priority.rs). The reward is the priority fee plus the half of the signature fees that is not burned. The cost counts the requested compute limit, 720 units per signature, 300 per write lock, one per 4 bytes of instruction data and 8 per 32 KiB of requested loaded data (block_cost_limits.rs).

Firedancer takes the same reward and compares two transactions by cross-multiplying reward and cost, without the + 1 (fd_pack.c). BAM, Harmonic and Rakurai publish no sort, so there the Agave rule was the hypothesis under test.

The pair

So I sent pairs: two of my own transactions to the same leader, both writing to the same fresh account, so one has to go first. And I priced them so the rules disagree about the winner. In the first pair, Y pays more, but it also costs more. In the second, D pays more than A for each unit of compute, but it asks to load the maximum amount of account data. So per unit of cost, D pays five times less. Only reward per unit of cost puts Y first in one pair and A first in the other.

which rule puts which transaction first
pairreward ÷ costCU pricelamport feecost aloneno rule
C4: Y against AYYYAeither
C6: D against AADDAeither

Only reward per unit of cost puts Y first in C4 and A first in C6. Both members write to one fresh account, so one has to wait for the other. Each has its own fee payer, so neither can hold the other back.

Two moments

Timing matters too. Some pairs reached the leader about a second before its slot, so both waited in its buffer. For those, a hash picked which one I sent first. Other pairs arrived while the block was already being built, and I sent the better one 2, 10 or 40 milliseconds after the other.

how the timing works

A buffered pair leaves when the program sees the slot five before the leader's first one, about a second before the leader's first shred, so both members wait in its buffer and its sort sees them together. A window pair leaves when the program sees the leader's first slot, so both reach a leader that is already producing.

Which payer goes first is the parity of one byte of a hash seeded by the frozen plan, so the send order is random and anyone can recompute it. Each window gets its own QUIC connection, opened about 7 s before the pair, and both members travel on it.

Calibration

Before I trusted the method, I tried it on a question with a known answer. Since version 4.4, Agave breaks ties by arrival order. Version 4.3 doesn't. And that's exactly what I measured: 90 out of 90, against 46 out of 90.

the known answer

From v4.4.0 on, Agave keeps arrival order among equal priorities (#14836). Block T sent identical pairs to validators on both sides of that change. The treated validators put the first sent first in 90 of 90 pairs, and the controls on 4.3.2 in 46 of 90, which is what a coin gives.

Frozen on chain

I froze every plan in advance. Before each block of pairs, the hashes of the plan, the code and the target list went on chain in a Memo. If anything changes, the program refuses to send.

where the Memos are

Block T's Memo is in slot 464,002,340 and its first pair in slot 464,018,360. Block P's Memo is in slot 464,169,852 and its first pair in slot 464,170,012. python tools/reproduce.py --chain recomputes the hashes from the files in the repository and reads them back from both Memos.

From Kyiv

It all ran from a battery-backed home server in Kyiv, where planning around Solana's leader schedule turned out easier than planning around the blackout schedule. Three sessions in 48 hours, nearly 1,400 pairs.

the path

One host sent over QUIC straight to the TPU address each leader advertises, with no RPC node, no staked relay and no Jito. The sender holds no stake and stays under the limits for unstaked connections. If the slot clock was late or stale, the pair was cancelled rather than sent late, and the reason went into the data.

When the leader holds both

When the leader holds both transactions at once, the answer is clean. All ten validators, running eight different clients, sort them by reward per unit of cost. Y went first in all 215 pairs, and D in none of its 211.

When the block is being built

Once the block is being built, the picture splits. Agave, Jito Labs and Rakurai take transactions in the order they arrive. The better one got ahead only 4 times out of 119. Firedancer, BAM and Harmonic work in batches of tens of milliseconds. That's most likely why public tests disagree: a transaction sent through an RPC node often reaches the leader in the middle of a block.

the curve for each validator

Sent 2 to 40 ms after the other into a running block, the better transaction overtook 4 times in 119 on Jito Labs, Agave and Rakurai. On Firedancer it overtook every time at 2 and 10 ms and never at 40 ms. BAM and Harmonic fall in between. tools/horizon.py fits a batch period to each curve.

For each validator, the share of pairs in which the better transaction landed first: when the leader held both, and when it was sent 2, 10 or 40 ms after the other while the leader was producing. Jito Labs, Agave and Rakurai drop to near zero in the window; Firedancer stays at the top to 10 ms and drops at 40 ms; BAM and Harmonic decline more slowly.

All ten validators

Every validator ranked my transactions by reward per unit of cost, strictly, when it held both. They differ in the last two columns.

validatorclient and versioningressheld bothat equal priorityinside the blockscored pairs
3SkE34PVJitoLabs 4.3.2own TPUreward per costno relationarrival order138/144
HEL1USMZJitoLabs 4.4.0own TPUreward per costarrival orderarrival order143/144
7QQGNm3pAgave 4.3.2own TPUreward per costno relationarrival order136/144
ana2y2YvRakurai 4.3.2own TPUreward per costno relationarrival order96/96
9eGrDohdFiredancer 26.10.5own TPUreward per costreverse arrivalbatch of about 40 ms144/144
5Cchr1XGAgaveBam 4.3.2BAMreward per costno relationbatch of about 45 ms128/144
DRpbCBMxAgaveBam 4.4.0BAMreward per costreverse arrivalbatch of about 50 ms126/144
9jxgosAfFireBAM 26.10.5BAMreward per costno relationbatch over 78 ms129/144
Fd7btgySHarmonicAgave 4.4.0Harmonicreward per cost*no relationbatch of about 45 ms117/144
JUPiTERrHarmonicFiredancer 26.10.5Harmonicreward per cost*no relationbatch of about 36 ms106/144

* On Harmonic one probe kind went the other way in 7 of 7 pairs, so its key is close to Agave's and not the same. Client labels are what each validator reports in gossip. ana2y2Yv sat out the third session after it moved to Rakurai 4.4.0. Counts, intervals and sensitivity views are in REPORT.md.

Limits

I didn't measure the exact rule on Harmonic, the RPC path itself, or heavy transactions that fight over busy accounts. The repo lists every limit.

read this before quoting a number
  • A verdict describes the path measured: one unstaked sender writing straight to the leader's TPU, or to the BAM or Harmonic ingress in front of it.
  • The pairs are small, untipped and touch only fresh accounts. Bundles, heavy transactions and contended accounts are outside the study.
  • A verdict is about one validator in the sessions it ran in, not about every validator on its client.
  • No pair went through an RPC node. What this page says about measurements that do comes from the window pairs and their timing, not from a measurement.

Check it yourself

You don't have to trust me. One command rebuilds every number from the raw data, offline and without keys, and you can check every pair on chain with getBlock.

rebuild every number

git clone https://github.com/souschuk/inside-the-block && cd inside-the-block && docker compose up --build

It runs the tests, then recomputes the reports for both blocks from data/ and compares them with the published ones.

look up the first pair of block P

curl -s https://api.mainnet-beta.solana.com -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"getBlock","params":[464170012,{"encoding":"json","transactionDetails":"signatures","rewards":false,"maxSupportedTransactionVersion":1}]}'

Its two signatures sit at indexes 10 and 11 of the 359 in result.signatures.

A NAVISTRI report by Maksym Sushchuk, made for the Superteam Ukraine bounty of the same name. Questions about the method or the data: max@sushch.uk. Type cd ~ for the other experiments.