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.
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 loadthe 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
pair
reward ÷ cost
CU price
lamport fee
cost alone
no rule
C4: Y against A
Y
Y
Y
A
either
C6: D against A
A
D
D
A
either
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 one2, 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 listwent 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 easierthan 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.
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.
validator
client and version
ingress
held both
at equal priority
inside the block
scored pairs
3SkE34PV
JitoLabs 4.3.2
own TPU
reward per cost
no relation
arrival order
138/144
HEL1USMZ
JitoLabs 4.4.0
own TPU
reward per cost
arrival order
arrival order
143/144
7QQGNm3p
Agave 4.3.2
own TPU
reward per cost
no relation
arrival order
136/144
ana2y2Yv
Rakurai 4.3.2
own TPU
reward per cost
no relation
arrival order
96/96
9eGrDohd
Firedancer 26.10.5
own TPU
reward per cost
reverse arrival
batch of about 40 ms
144/144
5Cchr1XG
AgaveBam 4.3.2
BAM
reward per cost
no relation
batch of about 45 ms
128/144
DRpbCBMx
AgaveBam 4.4.0
BAM
reward per cost
reverse arrival
batch of about 50 ms
126/144
9jxgosAf
FireBAM 26.10.5
BAM
reward per cost
no relation
batch over 78 ms
129/144
Fd7btgyS
HarmonicAgave 4.4.0
Harmonic
reward per cost*
no relation
batch of about 45 ms
117/144
JUPiTERr
HarmonicFiredancer 26.10.5
Harmonic
reward per cost*
no relation
batch of about 36 ms
106/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 measurethe 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.
---title: Inside the Block: how Solana validators order transactions
author: Maksym Sushchuk (NAVISTRI)
date: 2026-11-04
window: Solana mainnet, 1 to 3 November 2026
repo: https://github.com/souschuk/inside-the-block
video: https://youtu.be/Qy4snotpZkc
---# 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.
Video (4:30): [youtu.be/Qy4snotpZkc](https://youtu.be/Qy4snotpZkc)
## answer
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.
## question: 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.
## block: 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: 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](https://github.com/anza-xyz/agave/blob/v4.3.2/core/src/transaction_priority.rs#L32-L66)).
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](https://github.com/anza-xyz/agave/blob/v4.3.2/cost-model/src/block_cost_limits.rs#L8-L20)).
Firedancer takes the same reward and compares two transactions by cross-multiplying reward
and cost, without the + 1
([fd_pack.c](https://github.com/firedancer-io/firedancer/blob/v26.10.5/src/disco/pack/fd_pack.c#L1040-L1063)).
BAM, Harmonic and Rakurai publish no sort, so there the Agave rule was the hypothesis under
test.
## pair: 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:
| pair | reward ÷ cost | CU price | lamport fee | cost alone | no rule |
|-----------------|---------------|----------|-------------|------------|---------|
| C4: Y against A | Y | Y | Y | A | either |
| C6: D against A | A | D | D | A | either |
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.
## moments: 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: 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](https://github.com/anza-xyz/agave/pull/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: 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.
## kyiv: 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.
## held: 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.
## window: 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.
Figure: [curve.svg](https://navistri.com/reports/inside-the-block/curve.svg). 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.
## results: 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.
| validator | client | equal priority | inside the block | pairs |
|-----------|----------------------------|-----------------|------------------|---------|
| 3SkE34PV | JitoLabs 4.3.2 | no relation | arrival order | 138/144 |
| HEL1USMZ | JitoLabs 4.4.0 | arrival order | arrival order | 143/144 |
| 7QQGNm3p | Agave 4.3.2 | no relation | arrival order | 136/144 |
| ana2y2Yv | Rakurai 4.3.2 | no relation | arrival order | 96/96 |
| 9eGrDohd | Firedancer 26.10.5 | reverse arrival | batch ~40 ms | 144/144 |
| 5Cchr1XG | AgaveBam 4.3.2 | no relation | batch ~45 ms | 128/144 |
| DRpbCBMx | AgaveBam 4.4.0 | reverse arrival | batch ~50 ms | 126/144 |
| 9jxgosAf | FireBAM 26.10.5 | no relation | batch >78 ms | 129/144 |
| Fd7btgyS | HarmonicAgave 4.4.0 | no relation | batch ~45 ms | 117/144 |
| JUPiTERr | HarmonicFiredancer 26.10.5 | no relation | batch ~36 ms | 106/144 |
On the two Harmonic validators one probe kind went the other way in 7 of 7 pairs, so the key
there 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](https://github.com/souschuk/inside-the-block/blob/main/REPORT.md).
## limits: 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: 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:
```bash
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:
```bash
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.
Repository: [github.com/souschuk/inside-the-block](https://github.com/souschuk/inside-the-block)
Questions about the method or the data: [max@sushch.uk](mailto:max@sushch.uk).
<!-- one unstaked sender on a home battery, and every leader still showed its rule. -->