Stake-weighted QoS, explained properly

Fundamentals16 min read

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.


the short version
  • 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.

leaders.ts
1import { Connection } from "@solana/web3.js";
2
3const rpc = new Connection(process.env.RPC_URL!, "confirmed");
4
5/**
6 * Who is producing blocks right now, and who is next.
7 *
8 * Leaders serve four consecutive slots each, so the schedule is chunked in
9 * fours. A sender that wants to reach the leader directly has to know which
10 * 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);
15
16 // 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}
23
24const 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:

IdentityTreatment
A staked validatorAdmitted against that validator’s proportional share of the staked budget
Unknown or unstakedCompetes for the shared unstaked remainder
Over its share, or the pool is fullConnection 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.

share.ts
1import { Connection } from "@solana/web3.js";
2
3const rpc = new Connection(process.env.RPC_URL!, "confirmed");
4
5/**
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 identity
9 * can claim. It is a proportion of total activated stake, so it moves slowly
10 * 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;
17
18 return validator.activatedStake / total;
19}
20
21const share = await stakeShare(SOME_VALIDATOR);
22console.log(`${(share * 100).toFixed(4)}% of activated stake`);
23
24// A validator with 0.5% of stake commands roughly 0.5% of the leader's
25// staked connection capacity. That sounds small until you compare it with
26// 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.

These figures move with validator configuration and Solana releases, so treat them as illustrating the shape rather than as constants to hard-code. The proportional relationship is the stable part.

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.

identity.rs
1use solana_keypair::Keypair;
2use solana_signer::Signer;
3
4/// A TPU client does not send a bearer token. It proves who it is with a
5/// self-signed certificate carrying an Ed25519 public key, presented during
6/// the QUIC handshake.
7///
8/// The leader reads the public key out of the peer certificate and looks it up
9/// against the current stake map. If that identity is a staked validator, the
10/// connection is admitted against that validator's share. If it is unknown, the
11/// connection competes for the unstaked remainder.
12///
13/// This is the entire mechanism. There is no header, no API key, and nothing
14/// you can set on a request. Your position in the queue is decided before you
15/// 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}
20
21// The consequence worth internalising: stake is a property of the CONNECTION,
22// not of the transaction. You cannot pay your way into a staked connection
23// with a priority fee. The fee competes for ordering once you are already
24// 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.

probe.sh
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 to
5# work. Test the whole thing, not the port.
6
7timeout 8 openssl s_client \
8 -quic \
9 -connect send.swqos.com:11000 \
10 -servername send.swqos.com \
11 -alpn solana-tpu \
12 -brief </dev/null
13
14# CONNECTION ESTABLISHED
15# Protocol version: QUICv1
16# Ciphersuite: TLS_AES_256_GCM_SHA384
17# 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.

RouteWhat it costsWhat you get
Run a staked validatorStake, hardware, and continuous operationsYour own allocation, shared with nobody
Stake-weighted access from a validatorA commercial arrangement, often a large oneA share of their allocation, on their terms
A relay with staked connectionsPer transactionDelivery 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.
Do not reach for delivery infrastructure first. It is the most expensive fix on the list and it is not the most common cause. Fix your blockhash handling, price your fee from live per-account data, measure your compute units. If the loss rate is still flat after all three, then you are queueing at admission, and this is the mechanism that decided it.
Stop guessing at delivery.

swqos.com forwards your signed transactions to the leader over staked connections held open and kept warm. One prepaid balance, a flat price per send.

Get an API key
keep reading