Skip to content

Ticks, Slots, and Hashes-per-Tick

The previous page, Stamping Events Into Time, showed how a single event gets sealed into the hash chain: one hash over sha256(state || event) fixes its position so it can never be moved without redoing all the work that follows. That gives you an order. But an order is not yet a clock anyone can plan around. A clock needs a cadence — a steady beat that advances whether or not anything is happening — and it needs that beat to line up with real, wall-clock time so the rest of the machine can schedule against it.

This page turns the raw hash count into a usable timeline. It does that with three ideas stacked on top of each other: ticks (a fixed-size unit of “bare clock” progress), hashes-per-tick (the knob that calibrates a tick to real time on reference hardware), and slots (a fixed number of ticks — the chunk of time one leader is responsible for). Get those three straight and the payoff falls out for free: because the cadence is predictable, the schedule of who leads which slot is known in advance.

Recall the two operations the generator exposes, from rust/solmini/src/poh.rs. tick advances the bare clock and emits an entry with no event mixed in; record does the same but folds an event into the last hash:

// A bare tick: "time passed and nothing happened."
pub fn tick(&mut self, n: u64) -> Entry {
for _ in 0..n {
self.state = hash_once(&self.state); // h = sha256(h)
self.count += 1;
}
Entry { num_hashes: n, hash: self.state, mixin: None } // mixin: None == a pure tick
}

The important thing about tick is the mixin: None. It records elapsed work with no event attached. This is the piece that makes PoH a clock rather than merely a log of events. A log only has entries when something happens; if the network goes quiet for a moment, a log has nothing to say. PoH’s clock, by contrast, keeps hashing regardless — the leader emits tick entries into the empty space, and every one of them is provable elapsed work. The chain advances whether or not transactions arrive.

busy stretch quiet stretch busy again
─────────────── ───────────────────── ───────────────
record record tick tick tick tick tick record tick record
│ │ │ │
events sealed only bare ticks — the clock more events,
into the stream keeps beating with no events on a clock that
never stopped

Without ticks, “nothing happened for a while” would be invisible in the chain — the next event would appear to follow the previous one immediately. Ticks make idleness itself provable: a run of mixin: None entries is a signed statement that this much sequential work elapsed and no events were recorded during it.

Hashes-per-tick: calibrating the beat to real time

Section titled “Hashes-per-tick: calibrating the beat to real time”

A tick is worthless as a time unit until you say how many bare hashes make one tick. That single number — call it hashes_per_tick — is what pins the abstract chain to the physical world.

Here is the reasoning, from first principles. On a given machine a SHA-256 hash costs a roughly fixed amount of time. So a fixed count of hashes costs a roughly fixed duration. If you know your reference hardware can compute, say, a few million hashes per second, you can choose hashes_per_tick so that one tick lands on whatever wall-clock interval you want:

Notice what hashes_per_tick is really doing: it is a speed governor. The network agrees that a valid tick must contain at least that many hashes. A leader whose hardware is faster than the reference cannot race ahead and produce ticks quicker than the target rate would allow, because a tick is defined by its hash count, not by the leader’s wall clock. A slow leader falls behind and simply produces fewer ticks in its window. The count is the unit of account; the hardware only decides how fast you can mint it.

Under the hood — a tick as a boundary, not a payload

Section titled “Under the hood — a tick as a boundary, not a payload”

It is tempting to picture a tick as “a hash.” It is cleaner to picture it as a boundary drawn every hashes_per_tick hashes. The generator hashes continuously; every time the running count crosses a multiple of hashes_per_tick, it emits a tick entry pinning the chain state at that boundary. Events are recorded between boundaries. So the stream a leader broadcasts is a sequence of entries where the mixin: None entries are the metronome and the mixin: Some(..) entries are the events, both stamped into the same monotonic hash count:

count: 0 12,500 25,000 37,500 50,000
│ tick │ tick │ tick │ tick │
├────────┼─────────┼──────────┼──────────┤
▲record ▲record
event folded in event folded in
at count 8,140 at count 41,002

Every entry — tick or record — carries num_hashes (work since the previous entry) and the resulting hash, so a verifier reconstructs the whole timeline, boundaries and events alike, by replaying it. The tick boundaries cost nothing extra to verify; they are just more hashes in the same chain the verifier was already going to recompute (that parallel recompute is the subject of the next page, Verifying the Clock in Parallel).

A tick is a fine-grained beat. But you don’t hand out leadership one tick at a time — that would be absurd churn. You group ticks into a coarser unit: a slot is a fixed number of ticks, and it is the chunk of time a single leader is responsible for filling.

1 slot = fixed number of ticks (illustratively, ~64)
1 tick = fixed number of hashes (hashes_per_tick, illustratively ~12,500)
┌──────────────────────── one slot ────────────────────────┐
│ tick · tick · tick · … · tick · tick (e.g. 64 ticks) │
└───────────────────────────────────────────────────────────┘
▲ one leader owns this whole window and streams
its transactions (records) plus the ticks into PoH

For the length of a slot, exactly one validator is the leader: it holds the PoH generator, streams incoming transactions in as record entries, fills the gaps with tick entries, and broadcasts the resulting stream. When the slot’s tick budget is spent, leadership passes to the next validator for the next slot, whose PoH continues from where the previous leader left off. Because a slot is defined as a count of ticks — and a tick as a count of hashes — a slot is ultimately just a fixed amount of sequential work, which on reference hardware is a fixed span of wall-clock time (the ~400 ms figure above).

This is the level at which the earlier chapters connect: PoH gives you order and a beat; a slot is the convenient bucket that says “these entries, this leader, this ~400 ms.” Consensus (covered later, in Order First, Vote Second) then votes on slots, not on individual hashes.

The payoff: a predictable cadence means a known schedule

Section titled “The payoff: a predictable cadence means a known schedule”

Here is why all this bookkeeping is worth it.

Because a slot is a fixed amount of work that elapses in a predictable amount of time, slots advance at a known rate that the whole network agrees on without talking. Slot 100 follows slot 99 after one slot’s worth of ticks — no messaging required to establish “it is now slot 100.” And if everyone agrees on the rate at which slots advance, then everyone can agree ahead of time on who leads which slot.

That is the leader schedule: a deterministic assignment, computed from stake at the start of an epoch, of a leader to every upcoming slot for a long stretch of future slots. Every validator can compute the identical schedule locally, because its only inputs are the (already-agreed) stake distribution and the (already-agreed) slot numbering that PoH’s cadence guarantees.

slot: 999 1000 1001 1002 1003 …
leader: V_a V_b V_b V_c V_a …
└─ known now, before any of these slots have happened ─┘

Why does a known-in-advance schedule matter so much? Because it removes the last big conversation from the hot path. On a chain without a predictable clock, you do not know who will produce the next block until it appears, so you gossip transactions to everyone and hope. On Solana, since you already know that validator V_b leads slots 1000–1001 and V_c leads slot 1002, you can forward your transaction straight to the upcoming leader(s) before their slot even begins — no mempool, no broadcast-and-pray. This mempool-less forwarding (Gulf Stream, in Solana’s own naming) is a direct dividend of the predictable cadence built on this page. We only earn it here; later parts of the machine spend it.

The chain of dependencies, stated as a single line to hold in your head:

hashes-per-tick fixes a tick’s duration → ticks-per-slot fixes a slot’s duration → a fixed slot duration makes the leader schedule computable in advance → a known leader lets you forward transactions instead of flooding them.

  • Why does it exist? Raw PoH gives you a tamper-proof order and a hash count, but not a beat you can plan against. Ticks, hashes-per-tick, and slots exist to turn that raw count into a shared, wall-clock timeline with named, schedulable units.
  • What problem does it solve? It gives the chain a steady cadence that advances even when idle (via bare ticks) and a predictable slot rate — which is what makes the leader schedule computable ahead of time and, downstream, makes mempool-less transaction forwarding possible.
  • What are the trade-offs? The calibration is only as honest as the reference-hardware assumption: hashes-per-tick is a governor tuned for a typical validator, so faster hardware is throttled and slower hardware can fall behind and miss its slot. Shorter slots mean fresher ordering but leave less slack for network propagation, so the parameters are a genuine tension, not free choices.
  • When should I avoid it? If your system does not need a global, hardware-paced clock — a single machine, or a small trusted cluster with a real synchronized wall clock — this machinery is pure overhead. Its value only appears when many distrusting machines must agree on time without conversing.
  • What breaks if I remove it? Drop ticks and idle stretches become invisible, so the clock can stall silently. Drop the fixed hashes-per-tick/ticks-per-slot calibration and slot times drift with each leader’s hardware, making the leader schedule unpredictable — which collapses Gulf Stream-style forwarding and forces the network back to gossip-and-vote for ordering.
  1. What is the single most important thing about a tick entry’s mixin field, and why does that make PoH a clock rather than just an event log?
  2. Define hashes_per_tick from first principles. Given a reference hashrate and a target tick duration, how do you compute it, and why does it act as a speed governor on leaders?
  3. A slot is defined as a fixed number of ticks. Walk the definition all the way down: in terms of raw hashes and wall-clock time, what is a slot, and who is responsible for producing one?
  4. The illustrative figures on this page are ~400 ms slots and ~64 ticks/slot. Why must you treat these as calibration knobs rather than fundamentals — and what is the fundamental that survives across versions?
  5. Trace the payoff chain: how does a predictable cadence lead to a leader schedule known in advance, and why does knowing the upcoming leader let a client skip the mempool?
Show answers
  1. A tick’s mixin is None — it records elapsed sequential work with no event attached. That is what lets the chain keep advancing during idle stretches: the leader emits bare ticks into empty space, so “nothing happened for a while” becomes provable elapsed work instead of an invisible gap. An event log only has entries when something happens; PoH’s tick stream beats regardless, which is what makes it a clock.
  2. hashes_per_tick is how many bare hashes must be performed to constitute one tick. Since one SHA-256 hash costs a roughly fixed time on a given machine, hashes_per_tick ≈ target_tick_duration × reference_hashrate. It governs speed because a valid tick is defined by its hash count, not by the leader’s wall clock — faster hardware cannot mint ticks any quicker than the count allows, and slower hardware simply produces fewer of them and falls behind.
  3. A slot is a fixed number of ticks (illustratively ~64); a tick is a fixed number of hashes (hashes_per_tick). So a slot is ticks_per_slot × hashes_per_tick bare hashes of sequential work — e.g. 64 × 12,500 = 800,000 hashes — which on reference hardware is a fixed span of wall time (~400 ms). Exactly one validator, the leader for that slot, produces it: it streams transactions in as record entries and fills the gaps with tick entries.
  4. They are chosen to hit a target slot time on assumed reference hardware, and both the hardware and the protocol version change over time, so the exact numbers drift. The fundamental that survives is that a count of hashes converts to a duration once the machine is fixed — and therefore that a fixed count of ticks is a fixed, predictable span of time. The knobs are calibration; the count-to-time relationship is the principle.
  5. Because a slot is a fixed amount of work, slots advance at a rate the whole network agrees on without messaging, so everyone can compute the identical leader schedule (leader-per-slot, derived from stake) far ahead of time. Knowing that validator V_b leads the next slot means a client can forward its transaction directly to that upcoming leader instead of broadcasting to a mempool and hoping — that is the mempool-less forwarding the predictable cadence pays for.