Stake-weighted QoS, explained properly
Stake-weighted QoS is how a Solana leader decides whose packets to accept when more arrive than it can process. Connection capacity is allocated in proportion to stake, so a transaction sent through a staked connection competes in a much smaller queue than one sent through a public RPC.
- A leader can only accept so many QUIC connections. Stake-weighted QoS decides how that capacity is shared out.
- Capacity is allocated in proportion to activated stake. Unstaked senders compete for a small shared remainder.
- Stake is a property of the connection, established at the TLS handshake. A priority fee cannot buy you into it.
- It decides whether your packets are read at all. The fee only decides ordering once you are already inside.
Stake-weighted QoS is one of those pieces of Solana that everybody has heard of, most people can roughly gesture at, and very few can describe precisely enough to reason about. It is also, during congestion, the single mechanism that decides whether your transaction gets looked at.
So it is worth being exact.
The problem it solves#
A Solana leader produces blocks for four consecutive slots, roughly 1.6 seconds, and during that window every sender on the network wants to hand it transactions. Those arrive over QUIC.
A leader cannot accept unbounded connections. There is a finite budget of concurrent connections and a finite rate of streams it will read. When demand exceeds that budget, something has to be refused, and the interesting design question is what.
Refusing at random is the obvious answer and a bad one. It means:
- anyone can flood the leader with connections and crowd out legitimate traffic cheaply;
- a sender with no investment in the network has the same claim as one with a great deal;
- during exactly the moments that matter most, admission becomes a lottery.
Stake-weighted QoS answers it differently: allocate connection capacity in proportion to stake. A validator holding one percent of activated stake can claim roughly one percent of the leader’s staked connection budget. Everything unstaked shares what is left.
The property this buys is that crowding out the network requires acquiring stake, which is expensive, slow and public. That is a much better attack cost curve than opening more sockets.
How the allocation works#
Every four slots the leader changes, and the schedule is known in advance. A sender that wants to reach the leader directly needs to know who that is now and who it will be shortly.
1import { Connection } from "@solana/web3.js";23const rpc = new Connection(process.env.RPC_URL!, "confirmed");45/**6 * Who is producing blocks right now, and who is next.7 *8 * Leaders serve four consecutive slots each, so the schedule is chunked in9 * fours. A sender that wants to reach the leader directly has to know which10 * validator that is, and which one takes over in roughly 1.6 seconds.11 */12async function upcomingLeaders(count = 4) {13 const slot = await rpc.getSlot("confirmed");14 const leaders = await rpc.getSlotLeaders(slot, count * 4);1516 // Collapse the four-slot runs into distinct validators.17 const distinct: string[] = [];18 for (const leader of leaders.map((l) => l.toBase58())) {19 if (distinct[distinct.length - 1] !== leader) distinct.push(leader);20 }21 return distinct;22}2324const next = await upcomingLeaders();25console.log("current and upcoming leaders:", next);
When a connection arrives, the leader examines the client certificate presented during the QUIC handshake, extracts the Ed25519 public key, and looks that identity up against the current stake map. Three outcomes:
| Identity | Treatment |
|---|---|
| A staked validator | Admitted against that validator’s proportional share of the staked budget |
| Unknown or unstaked | Competes for the shared unstaked remainder |
| Over its share, or the pool is full | Connection refused, and nothing you sent is read |
The arithmetic#
The proportions are what make this concrete, and they are public. You can compute any validator’s share directly.
1import { Connection } from "@solana/web3.js";23const rpc = new Connection(process.env.RPC_URL!, "confirmed");45/**6 * What share of the leader's staked connection capacity a validator commands.7 *8 * This is the number that decides how much of the QoS budget a given identity9 * can claim. It is a proportion of total activated stake, so it moves slowly10 * and it is public.11 */12async function stakeShare(votePubkey: string) {13 const { current } = await rpc.getVoteAccounts();14 const total = current.reduce((sum, v) => sum + v.activatedStake, 0);15 const validator = current.find((v) => v.votePubkey === votePubkey);16 if (!validator) return 0;1718 return validator.activatedStake / total;19}2021const share = await stakeShare(SOME_VALIDATOR);22console.log(`${(share * 100).toFixed(4)}% of activated stake`);2324// A validator with 0.5% of stake commands roughly 0.5% of the leader's25// staked connection capacity. That sounds small until you compare it with26// the unstaked pool, which every unstaked sender on the network shares.
Work an example. Suppose a leader has a staked connection budget of 2,000 concurrent connections, and a small unstaked pool alongside it.
- A validator with 1% of stake can claim around 20 connections. Comfortable.
- A validator with 0.05% of stake can claim around 1. Enough, if it is used well.
- Everything unstaked, which is most of the network by count, contends for the remainder.
The asymmetry is the point. Even a small stake share puts you in a queue with a handful of competitors instead of a queue with everyone.
Where stake enters the handshake#
This is the detail that clarifies everything else. There is no header. There is no field on the transaction. Identity is established by the certificate presented at the TLS handshake, before any transaction has been sent.
1use solana_keypair::Keypair;2use solana_signer::Signer;34/// A TPU client does not send a bearer token. It proves who it is with a5/// self-signed certificate carrying an Ed25519 public key, presented during6/// the QUIC handshake.7///8/// The leader reads the public key out of the peer certificate and looks it up9/// against the current stake map. If that identity is a staked validator, the10/// connection is admitted against that validator's share. If it is unknown, the11/// connection competes for the unstaked remainder.12///13/// This is the entire mechanism. There is no header, no API key, and nothing14/// you can set on a request. Your position in the queue is decided before you15/// have sent a single byte of transaction data.16fn client_certificate(identity: &Keypair) -> (rustls::pki_types::CertificateDer<'static>,17 rustls::pki_types::PrivateKeyDer<'static>) {18 solana_tls_utils::new_dummy_x509_certificate(identity)19}2021// The consequence worth internalising: stake is a property of the CONNECTION,22// not of the transaction. You cannot pay your way into a staked connection23// with a priority fee. The fee competes for ordering once you are already24// inside. QoS decides whether you get in at all.
Which gives us the sentence worth memorising: stake is a property of the connection, not of the transaction.
Everything else follows from that. You cannot upgrade a transaction into a staked lane. The lane was decided when the socket was opened.
Four misconceptions#
“A higher priority fee gets me better QoS”
No. They operate at different layers and in a strict order. QoS decides whether the leader reads your packets at all. The priority fee decides how your transaction is ordered against others that have already been read. A generous fee on a connection that was refused is worth nothing, because nobody ever saw it.
This ordering explains a pattern people find baffling: raising the fee produces steadily better results, then abruptly stops producing any improvement at all. The point where it stops is the point where you switched from losing on ordering to losing on admission.
“My RPC provider is staked, so I get staked treatment”
Partly, and the part that is missing matters. Your provider’s stake determines its connection allocation. That allocation is then shared across its entire customer base. You are inside the staked lane, but so is everyone else who pays that provider, and you are queueing against them for the provider’s slice.
Whether that is fine depends entirely on the ratio of the slice to the customer count, which is not a number anyone publishes.
“It only matters during congestion”
True, and it is a smaller comfort than it sounds. Congestion is not a rare failure mode, it is precisely the condition in which your transaction is valuable. A launch, a liquidation cascade, a volatile hour: the moments when landing is worth most are exactly the moments when admission is contended.
Optimising for the quiet case is optimising for the case you do not care about.
“I can just run my own validator”
You can, and people do. Be clear-eyed about what it costs: acquiring meaningful stake, running the hardware, keeping it healthy, and the operational burden of a validator that now sits on your critical path. For some operations that maths works. For most it does not, which is why the intermediate market exists.
Observing it from outside#
You cannot see a leader’s admission decisions. What you can verify is whether a given endpoint completes a QUIC handshake at all, which is a different question from whether a UDP port is open.
1# Does a QUIC endpoint actually complete a handshake, and with which ALPN?2#3# UDP reachability alone proves nothing: a QUIC Initial, the server response,4# ALPN negotiation, the certificate exchange and authentication all have to5# work. Test the whole thing, not the port.67timeout 8 openssl s_client \8 -quic \9 -connect send.swqos.com:11000 \10 -servername send.swqos.com \11 -alpn solana-tpu \12 -brief </dev/null1314# CONNECTION ESTABLISHED15# Protocol version: QUICv116# Ciphersuite: TLS_AES_256_GCM_SHA38417# Verification: OK
General UDP reachability proves very little. A valid QUIC Initial, a server response, ALPN negotiation, the certificate exchange and authentication all have to succeed. Probe the whole path.
For the indirect evidence, the diagnostic in why your transactions are not landing applies: if your loss rate is flat across fee percentile and blockhash freshness, you are losing at admission rather than at ordering.
Getting a staked connection#
Three routes, with genuinely different trade-offs.
| Route | What it costs | What you get |
|---|---|---|
| Run a staked validator | Stake, hardware, and continuous operations | Your own allocation, shared with nobody |
| Stake-weighted access from a validator | A commercial arrangement, often a large one | A share of their allocation, on their terms |
| A relay with staked connections | Per transaction | Delivery over their connections, no infrastructure |
We are the third of those, so read that row with appropriate suspicion. The honest framing is that all three solve the same admission problem, and which one is right is a question about your volume and your appetite for running infrastructure, not a question with one answer.
When it actually matters#
Being precise about this is more useful than insisting it always matters.
It matters a great deal when:
- you are competing for a specific event, and being second is worth nothing;
- you are sending during contention, when admission is the binding constraint;
- you have already fixed blockhash freshness, fees and compute, and losses have not moved;
- your loss rate is uncorrelated with anything you control.
It matters much less when:
- you send occasionally and a retry thirty seconds later is fine;
- you are not competing against anyone for the accounts you write;
- your real problem is one of the other five causes, which it very often is.