Alpenglow replaces Solana's consensus on mainnet on October 21, which cuts finality from about 12.8 seconds to about 150 ms and can break code that reads blocks, counts transactions or waits for the old gap between confirmed and finalized. Apps that only send transactions and read account state need no change.
What changes
Alpenglow changes how validators agree on a block, not how transactions execute, so the breakage sits in code that reads blocks and commitment levels. Anza announced on October 13 that mainnet activation is scheduled for Wednesday October 21 at about 14:50 UTC (Solana Compass). Anza has not said whether 14:50 UTC is when the feature gate opens or when consensus hands over, so expect the switch to play out over a window, not at one instant. Alpenglow has been live on devnet since September 25, so you can test today (Solana Foundation).
| Area | Before (TowerBFT) | After (Alpenglow) | What to do |
|---|---|---|---|
| Finality | About 12.8 seconds | About 150 ms after the block is proposed | Switch to finalized only after activation. Shorten timeouts and retries built around 12.8 seconds. |
| Commitment levels | confirmed lands about one slot out, finalized follows 12.8 seconds later | confirmed and finalized describe the same state, and confirmed is to be removed in a later release | Nothing breaks on activation day. After it, point new code at finalized. |
| Candidate blocks | One block per slot in practice | A slot can carry more than one candidate block, and stream updates carry a bank_id | Buffer by (slot, bank_id) and keep the bank that reaches confirmed. |
| Vote transactions | Inside blocks, a large share of all transactions | Not in blocks | Re-baseline TPS, transaction counts and alert thresholds. |
| Ticks | 64 tick entries per slot | Gone | Take block completeness from block meta and the block footer. |
Clock | Median of validator vote timestamps | Set by the block's leader within a bound | Re-check any logic that depends on how closely it tracks real time. |
| Sending, programs, fees, account reads | Unchanged | Unchanged | Nothing. |
The 150 ms is the time from block proposal to finality. It is not the slot time, which was changed by a separate upgrade. Sources for the table: the Solana Foundation's Alpenglow page and Triton One's integrator guide.
Why bank_id exists: a slot is no longer one block
A bank is a node's in-memory state of the ledger for one block, and bank_id is the number the node gives that bank. Until now the slot number was enough to name a block. Under Alpenglow it may not be, because a slot can briefly have two candidate blocks before the network settles on one.
Here is the failure it prevents. Your indexer stores transactions with the slot as the key. Slot 100 arrives twice, once as bank_id 7 and once as bank_id 9. The second write overwrites the first, and you may keep the block that is later abandoned. Nothing errors, and the bad rows look normal.
Three rules follow:
- One connection: key your buffer by
(slot, bank_id), keep the bank that reachesconfirmed, and drop the other. - Several providers:
bank_idis local to each node, so the same block can carry a different number on each connection. Reconcile across connections on the blockhash. - Only reading
finalized: you rarely see the problem, because only the retained block arrives. It matters most forprocessedandconfirmedconsumers.
Anza expects this in under 1% of slots, which is why it is easy to miss in testing.
What it looks like on devnet
On devnet, which already runs Alpenglow, a test transaction reached finalized about one second after I sent it, and the confirmed and finalized updates arrived within 1 ms of each other in six of seven runs.
I sent a 1,000-lamport self-transfer from a fresh wallet and opened three LaserStream gRPC subscriptions on the Newark devnet endpoint, one per commitment level, because a subscribe request carries a single commitment level (LaserStream quickstart). I signed the transaction first to know its signature, started the clock just before sendRawTransaction, and logged when each stream delivered it. From the second run on I also polled getSignatureStatuses on the Helius devnet RPC as an independent check.
| Run | processed (ms) | confirmed (ms) | finalized (ms) | RPC finalized (ms) |
|---|---|---|---|---|
| 1 | 575 | 1,066 | 1,065 | Not measured |
| 2 | 531 | 931 | 930 | 1,060 |
| 3 | 729 | 923 | 923 | 1,009 |
| 4 | 589 | 1,113 | 1,113 | 1,129 |
| 5 | 599 | 1,013 | 1,013 | 1,071 |
| 6 | 552 | 1,043 | 1,050 | 1,042 |
| 7 | 655 | 1,053 | 1,053 | 1,101 |
| Median | 589 | 1,043 | 1,050 | 1,066 |
All times are milliseconds from send. The last row is the median of the seven runs (six for the RPC column). In each run all three levels reported the same slot.
import { subscribe, CommitmentLevel } from 'helius-laserstream'
const config = {
apiKey: process.env.HELIUS_API_KEY!,
endpoint: 'https://laserstream-devnet-ewr.helius-rpc.com',
}
for (const [name, level] of [
['processed', CommitmentLevel.PROCESSED],
['confirmed', CommitmentLevel.CONFIRMED],
['finalized', CommitmentLevel.FINALIZED],
] as const) {
subscribe(config, {
transactions: { mine: { accountInclude: [wallet], accountExclude: [], accountRequired: [], vote: false, failed: false } },
commitment: level,
accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
}, async (data) => { /* match the signature, log elapsed ms */ }, async (err) => console.error(name, err))
}Here wallet is the devnet wallet's public key as a string. Three things to read from the numbers:
- One state, not two. Before Alpenglow,
finalizedtrailedconfirmedby about 12.8 seconds. Here the two arrived together, and the RPC reportedfinalizedat about the same time as the stream. - This is not the 150 ms figure. My clock starts at send, so it includes my round trip to the endpoint and the wait to land in a block. Anza's 150 ms starts when the block is proposed.
- Do not rely on order across streams. Across the seven runs the two landed within 1 ms of each other six times and 7 ms apart once. In two runs
finalizedwas logged 1 ms beforeconfirmed, and in one run confirmed came first. These were separate connections, so I read that as noise. Helius documents that LaserStream does not guarantee message ordering (Helius FAQ), and Triton One warns thatconfirmedandfinalizedupdates now arrive back to back (Triton One). Code that expectsconfirmedfirst will eventually misfire.
This is seven runs on devnet from one location with one wallet, so treat it as an illustration, not a benchmark. The script and the README are in the demo repo, so you can rerun it from your own location.
Before October 21: what to change
Seven changes cover most pipelines, and the first one is a trap: switching to finalized too early makes an app slower, not faster.
- Leave commitment levels alone until the switch. On mainnet today
finalizedstill means a wait of about 12.8 seconds. After activation, point new code atfinalizedand shorten any timeout or retry built around the old delay (Solana Foundation). - Detect the switch from the cluster, not the calendar. The new
getAgGenesisCertRPC method returnsnullwhile a cluster runs TowerBFT and a certificate once it has migrated. The CLI equivalent issolana alpenglow-genesis-info. A node older than Agave 4.3 answers "method not found", which is not the same as "not migrated yet". - Stop assuming one block per slot. Key your buffers by
(slot, bank_id), keep the bank that reachesconfirmed, and drop the rest. If you subscribe to whole blocks through Yellowstone, the Alpenglow release (v16.0.0-rc10+solana.4.3.0at the time of writing) buffers for you. Anza expects a second candidate block in under 1% of slots, so a bug here is rare and silent. - Merge providers on the blockhash.
bank_idis a counter each node assigns for itself, so the same block can be 7 on one connection and 12 on another. Keep one buffer per connection and reconcile across connections on the blockhash from block meta. - Re-baseline throughput numbers. Vote transactions leave blocks, so transaction counts and TPS charts fall sharply with no drop in real activity, and an alert like "fewer than N transactions in a block" can start firing (Triton One).
- Do not read time from ticks. Tick entries leave the ledger. Use block meta and the block footer to know a block is complete, and keep your own slot clock if you time anything inside a slot.
- Treat switch-day data as temporary. Triton One reports that for about 20 minutes during the switch, blocks hold only votes, and the cluster then discards some of those slots and restarts from a single agreed block. Trust only what reaches
finalized.
Feature detection for item 2, using @solana/kit 8.3.0 or later:
import { createSolanaRpc } from '@solana/kit'
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com')
const cert = await rpc.getAgGenesisCert().send()
const alpenglowIsLive = cert !== nullRun it against your own provider's endpoint once that node runs Agave 4.3, because an older node throws a "method not found" error instead of returning null.
Sources
All pages were opened on October 13, 2026.
- Solana Foundation: Alpenglow for finality, commitment levels,
bank_id, votes, ticks andgetAgGenesisCert. - Triton One: Alpenglow and your Triton stack for streaming, TPS, merging providers and switch-day behavior.
- Solana Compass: Anza sets the Alpenglow mainnet date for the October 21 date and the migration steps.
- Helius docs: LaserStream FAQ for the lack of a message ordering guarantee.
- Helius docs: LaserStream gRPC quickstart for the devnet endpoint and the single commitment level per request.