Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6. Validator wire protocols

6.1 Datagram boundary and dispatch

[SOL-WIR-0001] A gossip, repair, TPU-vote, or TVU receiver MUST treat one UDP datagram as one complete application message, enforce the 1,232-byte limit, and reject a message if its grammar does not consume the datagram exactly.

6.2 Gossip envelope and CRDS pull filter

Every gossip datagram begins with a u32 little-endian Protocol discriminant. A Vec<T> below is the Chapter 5 sequence form: an eight-byte little-endian count followed by that many T values.

Normative table.

DiscriminantVariantExact payload after discriminant
0PullRequestCrdsFilter, then one CrdsValue
1PullResponsesender public key [32], then Vec<CrdsValue>
2PushMessagesender public key [32], then Vec<CrdsValue>
3PruneMessageouter public key [32], then PruneData
4PingMessagePing [128]
5PongMessagePong [128]

[SOL-WIR-0002] A gossip decoder MUST reject a discriminant outside 0..=5, an invalid nested value, a payload larger than 1,232 bytes, or trailing bytes. The combined serialized CrdsValue bytes in a push or pull response MUST NOT exceed 1,188 bytes.

CrdsFilter is serialized in the following order. BitVec<u64> bit number i is bit (i mod 64)—least significant bit first—of block floor(i/64).

Normative table.

OrderCrdsFilter fieldExact encoding
1Bloom keysu64 count, then that many u64 little-endian seeds
2Bloom blocksoption tag u8; 0 for no blocks, or 1, a u64 block count, and that many u64 little-endian blocks
3Bloom bit lengthu64 little-endian
4Bloom num_bits_setu64 little-endian
5masku64 little-endian
6mask_bitsu32 little-endian

The Bloom phantom type has zero bytes. For a CRDS identity hash h[32], let hu = LE_decode(h[0..8]), low = (!0u64) >> mask_bits when mask_bits < 64 and 0 otherwise, and canonical_mask = mask | low. The hash belongs to the filter’s partition exactly when (hu | low) == canonical_mask.

For every Bloom seed k, calculate a seeded FNV-1a state over all 32 bytes of h: initialize state = k; for each byte b, set state = (state XOR b) * 0x00000100000001b3 mod 2^64. Its Bloom position is state mod bit_length. Membership is true only if the partition comparison passes, the keys and bit vector are both nonempty, and every seeded position is set.

[SOL-WIR-0003] An accepted pull filter MUST have mask_bits >= 6; its bit length MUST NOT exceed the capacity of its serialized blocks. A sender MUST set every low bit of mask selected by low, while a receiver MUST ignore those low bits by using canonical_mask as defined above.

[SOL-WIR-0004] In answering a PullRequest, a validator MUST consider only CRDS values whose identity hash belongs to the requested mask partition and is absent from the Bloom filter; a Bloom false positive may omit a value but MUST NOT make an out-of-partition value eligible.

6.3 CRDS values and payload grammars

A CrdsValue is a 64-byte Ed25519 signature followed by a CrdsData: a four-byte little-endian discriminant and its payload. Its cached identity is not serialized.

Normative table. CRDS discriminants and independent processing status.

Disc.Payload after discriminantDecoderReceiver sanitationCRDS insertionLocally originate in EReturn in ordinary pull
0legacy ContactInfo: zero bytesknown tag; payload decode failsunreachableunavailablenono
1index:u8, then Voteacceptrequire index < 32 and valid Voteaccept if the Section 6.3.1 comparison winsyes; sender chooses index < 12yes
2index:u8, then LowestSlotaccept only when index == 0require valid LowestSlotaccept if comparison winsyesyes
3legacy SnapshotHashes: zero bytesknown tag; payload decode failsunreachableunavailablenono
4AccountsHashes: zero bytesknown tag; payload decode failsunreachableunavailablenono
5index:u8, then EpochSlotsacceptrequire index < 255 and valid EpochSlotsaccept if comparison winsyesyes
6legacy Version: zero bytesknown tag; payload decode failsunreachableunavailablenono
7Version: zero bytesknown tag; payload decode failsunreachableunavailablenono
8NodeInstance: zero bytesknown tag; payload decode failsunreachableunavailable; no duplicate transitionnono
9index:u16, then DuplicateShredacceptrequire index < 512 and valid chunk metadataaccept if comparison winsyesyes
10SnapshotHashesacceptrequire valid bounded hash listsaccept if comparison winsyesyes
11ContactInfoacceptrequire Section 6.5 validityaccept if comparison winsyesyes
12RestartLastVotedForkSlots belowacceptrequire valid bounded fieldsaccept if comparison winsonly during a coordinated restartno
13RestartHeaviestFork belowacceptrequire valid bounded fieldsaccept if comparison winsonly during a coordinated restartno

For discriminants 0, 3, 4, 6, 7, and 8, “known tag” means only that the enum discriminant is assigned. The epoch-E decoder deliberately returns an error while decoding its zero-sized deprecated payload, before signature verification, sanitation, label derivation, or insertion. In particular, deprecated NodeInstance values are never compared as duplicates or retained in the CRDS table.

All wallclock and slot values identified as bounded in this section must be strictly less than 10^15. The exact non-ContactInfo payload layouts are:

Normative table.

PayloadExact field sequence
Votefrom[32], one Chapter 7 transaction, wallclock:u64
LowestSlotfrom[32], root:u64, lowest:u64, slots:u64 count followed by ascending unique u64 slots, stash:u64 length followed by bytes, wallclock:u64
EpochSlotsfrom[32], u64 count of CompressedSlots, those values, wallclock:u64
SnapshotHashesfrom[32], full snapshot (slot:u64,hash[32]), u64 incremental count, that many (slot:u64,hash[32]), wallclock:u64
DuplicateShredfrom[32], wallclock:u64, slot:u64, unused u32, unused u8, num_chunks:u8, chunk_index:u8, chunk:u64 length followed by bytes
RestartLastVotedForkSlotsfrom[32], wallclock:u64, SlotsOffsets, last_voted_slot:u64, last_voted_hash[32], shred_version:u16
RestartHeaviestForkfrom[32], wallclock:u64, last_slot:u64, last_slot_hash[32], observed_stake:u64, shred_version:u16

A CompressedSlots starts with a u32 discriminant and has one of these layouts. BitVec<u8> uses the same option/count/bit-length grammar as the Bloom bit vector, except its blocks are bytes and bits within a byte are least-significant-bit first.

Normative table.

DiscriminantFormExact fields after discriminant
0raw DEFLATEfirst_slot:u64, num:u64, byte-string length u64, then raw DEFLATE bytes
1uncompressedfirst_slot:u64, num:u64, BitVec<u8>

The compressed form is RFC 1951 DEFLATE without a zlib header. Inflation must terminate successfully; the output bytes form the BitVec blocks. For either form, first_slot < 10^15, num < 16,384, and selected bit i denotes slot first_slot + i for 0 <= i < num. An uncompressed vector’s bit length must be a multiple of eight and equal its block capacity.

SlotsOffsets starts with u32 discriminant 0 for run-length encoding or 1 for raw offsets. Run-length encoding is a u64 element count followed by canonical unsigned-LEB128 u16 run lengths; runs alternate selected, unselected, selected, beginning with selected. Raw offsets is BitVec<u8>. In either form selected offset i denotes last_voted_slot - i; an underflow does not denote a slot.

[SOL-WIR-0005] A CRDS Vote transaction MUST satisfy Chapter 7 transaction sanitation and decode as a vote instruction; LowestSlot MUST have zero root, empty slots, empty stash, and bounded lowest and wallclock. Every SnapshotHashes incremental slot MUST be bounded and strictly greater than the full-snapshot slot; a sender MUST NOT publish more than 25 incremental hashes in one value.

[SOL-WIR-0006] A CRDS-valid DuplicateShred chunk MUST have num_chunks > 0 and chunk_index < num_chunks; insertion does not impose the assembly limit. A duplicate-proof consumer MUST discard a chunk from assembly when num_chunks > 3. Assembly state is keyed by (from,slot), not by the outer CRDS index. Receiving an assembly-eligible chunk replaces the buffered chunk at its chunk_index; when the number of occupied indices equals the received num_chunks, every buffered chunk MUST agree on slot and num_chunks. Concatenating chunk bytes in index order 0..num_chunks-1 then yields exactly u64 length || shred1 || u64 length || shred2. Both nested shreds MUST pass Chapter 9 and constitute a valid same-slot duplicate proof. The outer index is only the CRDS table label and MUST NOT partition assembly state.

[SOL-WIR-0007] A CrdsValue signature MUST verify under the from or ContactInfo public key contained in its payload over the exact bytes beginning with the CrdsData discriminant. Its identity MUST be SHA256(signature[64] || serialized_CrdsData). A PullRequest’s embedded value MUST be discriminant 11 ContactInfo; every pushed or returned value MUST pass its payload constraints and strict signature verification.

6.3.1 CRDS replicated-table transition

Normative table. CRDS label derivation.

ValueTable label
Vote(Vote, index, from)
LowestSlot(LowestSlot, from); the encoded index is constrained to zero and is not part of the label
EpochSlots(EpochSlots, index, from)
DuplicateShred(DuplicateShred, index, from)
SnapshotHashes(SnapshotHashes, from)
ContactInfo(ContactInfo, public_key)
RestartLastVotedForkSlots(RestartLastVotedForkSlots, from)
RestartHeaviestFork(RestartHeaviestFork, from)

For a label already present, new replaces old according to this total order. ContactInfo first compares (outset, wallclock) lexicographically when the public keys agree. If those tuples differ, the larger tuple wins. For all other cases, and for equal ContactInfo tuples, larger wallclock wins; equal wallclocks are broken by the lexicographically larger 32-byte CRDS identity hash. Replacing a value records the displaced hash in the recently-purged set. An unequal losing value records its own hash there. An equal hash is a duplicate: it leaves table bytes and insertion order unchanged; a push-route duplicate increments only its saturating local duplicate count.

[SOL-WIR-0008] A CRDS table MUST derive labels and apply replacement in the order above. An implementation MUST NOT merge two values sharing an origin but having different indexed labels. In particular, EpochSlots is a logical union only when consumed: decode each surviving indexed value, union selected slots for the origin, remove duplicates, and order the resulting slots ascending.

Push accepts a signed value only when its wallclock is in the inclusive window [now-15,000 ms, now+15,000 ms]. Pull accepts a caller ContactInfo only when its wallclock is in the half-open window [now-15,000 ms, now+15,000 ms). One responder-wide random jitter j in 0..3,750 ms is added to the caller wallclock; a pull response selects, in table insertion order, nondeprecated values matching the requested partition, absent from the Bloom filter, satisfying value.wallclock <= caller+j, and fitting the response byte budget. A receiver inserts an otherwise-new pull value if now <= value.wallclock + timeout(origin); an expired value may be retained only when ContactInfo for its origin already exists, so it can assist replacement and purge decisions.

The default origin timeout is 15,000 ms. The local identity never expires. An identity with positive epoch stake uses max(15,000 ms, epoch_duration_ms); when every supplied stake is zero, all identities use that extended timeout. If an origin’s ContactInfo has min(value.wallclock, local_receive_time)+timeout > now, every label of that origin is retained. Otherwise each label expires when the same expression is <= now. Displaced and rejected-outdated hashes remain in pull Bloom filters for 20,000 ms.

The preceding windows, insertion-order response selection, retention timeouts, and Bloom-filter retention describe the active network’s liveness profile. They are informative: local time and peer observation are not replicated state, and a different safe retention or selection policy does not make a bank invalid. Signed-value constraints, labels, replacement order, filter partitioning, response byte grammar, and receiver-side revalidation remain normative.

[SOL-WIR-0009] Push duplicate suppression, pull selection, expiration, and purge MAY vary without changing protocol state. A pull response MUST contain only individually valid values in the requested partition and MUST NOT knowingly include a value selected by the requester’s Bloom filter. The receiver MUST independently apply SOL-WIR-0007 and SOL-WIR-0008. Completed DuplicateShred evidence MUST follow SOL-WIR-0006; incomplete assembly may be evicted at any time and has no consensus effect.

The push active set contains 12 peers per stake bucket and sends each eligible new value to the first nine unpruned peers. There are 25 buckets. For stake s lamports define b(s)=min(24, bit_length(floor(s/1,000,000,000))); a value owned by o uses bucket min(b(local_stake),b(stake(o))). Peer sampling is without replacement with weight (min(peer_bucket,bucket)+1)^2; zero-weight peers follow positive-weight peers. A valid Prune applies only at its named destination, only within 500 ms of local time, and marks each named origin as suppressed for pushes to the prune sender in the applicable bucket. A node’s own values are never suppressed by a prune false positive.

[SOL-WIR-0010] A sender MUST honor authenticated, timely prune state. Active-set size, stake buckets, sampling, rotation timing, and fanout cap are informative liveness policy. A sender MUST NOT send a value to a peer for which the applicable prune filter positively contains that origin, except its own origin as stated above.

6.4 Ping, pong, and prune

Normative table.

ObjectByte rangeField
Ping[0,32)from public key
Ping[32,64)32-byte token
Ping[64,128)signature
Pong[0,32)from public key
Pong[32,64)32-byte token hash
Pong[64,128)signature

[SOL-WIR-0011] A Ping signature MUST verify under from over the 32 token bytes. Its expected Pong hash MUST be SHA256("SOLANA_PING_PONG" || token), where the prefix is the 16 ASCII bytes. A Pong signature MUST verify under its from key over the hash, and a Pong used as reachability evidence MUST match an outstanding Ping.

Normative table.

OrderPruneData fieldExact encoding
1pubkey32 bytes
2pruned keysu64 count followed by 32 bytes per key
3signature64 bytes
4destination32 bytes
5wallclocku64 little-endian

[SOL-WIR-0012] A PruneMessage MUST have an outer key equal to PruneData.pubkey, no more than 32 pruned keys, and wallclock < 10^15. The signature MUST verify over the serialization of (pubkey, pruned keys, destination, wallclock), either directly or preceded by the byte string ff 53 4f 4c 41 4e 41 5f 50 52 55 4e 45 5f 44 41 54 41; the prefixed form includes LE(18,8) before those 18 prefix bytes.

6.5 ContactInfo

Mainnet uses shred version 50093 in epoch E. ContactInfo discriminant 11 has this exact field order:

Normative table.

OrderFieldExact encoding
1public key32 bytes
2wallclockcanonical unsigned-LEB128 u64, milliseconds
3outsetu64 little-endian, microseconds
4shred versionu16 little-endian
5version majorcanonical unsigned-LEB128 u16
6packed minorcanonical unsigned-LEB128 u16
7patch or prerelease numbercanonical unsigned-LEB128 u16
8source identifieru32 little-endian
9feature-set identifieru32 little-endian
10implementation identifiercanonical unsigned-LEB128 u16
11addressesShortU16 count followed by IpAddr records
12socketsShortU16 count followed by SocketEntry records
13extensionsShortU16 count followed by TLV records

The low 14 bits of packed minor are the minor version. Bits 14–15 select stable (0), release candidate (1), beta (2), or alpha (3). For stable, the next value is the semantic patch; otherwise the semantic patch is zero and the next value is the prerelease number.

Normative table.

Nested typeExact encoding
IPv4 IpAddrdiscriminant LE(0,4), then four network-order address octets
IPv6 IpAddrdiscriminant LE(1,4), then sixteen network-order address octets; rejected by ContactInfo sanitation
SocketEntrykey:u8, address index:u8, canonical unsigned-LEB128 offset:u16
extension TLVtype:u8, ShortU16 byte length, then exactly that many bytes

The socket port accumulator begins at zero. For each entry in encoded order, add its offset with checked u16 arithmetic; that accumulated port and the indexed address form the endpoint.

Normative table. ContactInfo socket-key registry.

KeyDefined endpointEpoch-E protocol use
0gossip UDPactive
1repair QUICrecognized; no repair QUIC service, and a placeholder port is permitted
2JSON-RPCdiscovery only; RPC is outside scope
3RPC pubsubdiscovery only; RPC is outside scope
4repair UDPactive
5TPU UDPreserved and not used; does not authorize UDP transaction ingestion
6TPU-forward UDPreserved and not used
7TPU-forward QUICactive
8TPU QUICactive
9TPU-vote UDPactive
10TVU UDPactive
11TVU QUICrecognized but inactive
12TPU-vote QUICactive
13Alpenglow votor QUICactive; QUIC DATAGRAM consensus messages

[SOL-WIR-0013] A valid ContactInfo MUST contain only unique IPv4 addresses and unique socket keys, every socket index MUST be in range, and every address MUST be referenced. Port addition MUST NOT overflow; port zero, unspecified, multicast, and IPv6 endpoints MUST be rejected.

[SOL-WIR-0014] ContactInfo wallclock MUST be less than 10^15. A peer used for Turbine or repair MUST advertise shred version 50093. Unknown extension types MUST be skipped by their declared length and unknown socket keys MUST NOT be assigned a known service meaning.

6.6 TPU QUIC, TPU-vote UDP, and TLS

[SOL-WIR-0015] Ordinary TPU and TPU-forward ingestion MUST use QUIC and MUST NOT accept transaction application data through ContactInfo keys 5 or 6. A TPU-vote UDP datagram contains exactly one Chapter 7 transaction with no application framing. A TPU, TPU-forward, or TPU-vote QUIC connection MUST negotiate the ten-byte ALPN solana-tpu, and each completed requester-initiated unidirectional stream MUST contain exactly one transaction. The stream is nonempty, consumes all bytes, and is at most 4,096 bytes. Chapter 7 then applies the format-specific transaction limit: 1,232 bytes for legacy and version 0, or 4,096 bytes for version 1. The UDP form remains limited to 1,232 bytes by Section 6.1.

[SOL-WIR-0016] A TPU receiver MUST pass only QUIC’s ordered, reassembled bytes from one completed unidirectional stream to the transaction decoder. It MUST reject a bidirectional stream, QUIC datagram application data, or bytes concatenated across streams. A packet gap leaves the stream incomplete until QUIC retransmission or timeout; it is not an application decode error. Transport certificates do not authorize a transaction; Chapter 7 signatures do.

Informative table. Active QUIC liveness and resource profile.

ParameterRequired value or accepted domain
QUIC TLS versionTLS 1.3 only, as required by QUIC v1
Key exchangeX25519
TLS 1.3 cipher suitesaccept AES-128-GCM-SHA256, AES-256-GCM-SHA384, and ChaCha20-Poly1305-SHA256
ALPNexactly solana-tpu
Per-stream receive window and application hard cap4,096 bytes
Connection receive window8,388,608 bytes
Initial bidirectional-stream limit0
Initial unidirectional-stream limit0, raised after handshake/QoS classification
Concurrent inbound unidirectional streamsafter handshake, the exact stake-and-RTT formula below; possible advertised values are unstaked 128..=896 and staked 128..=3,584 streams
Idle timeout30 seconds
Handshake timeout2 seconds from receipt of Initial through receipt of Handshake
Incomplete-stream chunk timeout2 seconds
QUIC DATAGRAMdisabled
Path migrationdisabled
0-RTTserver acceptance disabled; clients may attempt resumption but application bytes are processed only after a completed handshake
Default connection admissionat most 2,000 staked and 2,000 unstaked connections; 16 per staked identity and 8 per unstaked IP
Per-source connection rate8 new connections per source IP per minute with burst capacity 80; all sources together 2,500 connections/second with burst capacity 1,000
Stream arrival throttle500 new streams per millisecond

Let peer_stake and total_stake be unsigned epoch-stake lamports and let rtt_ms = min(floor(measured_RTT_in_microseconds / 1000),350). Compute the post-handshake unidirectional-stream limit with unsigned floor arithmetic:

Normative algorithm.

if peer is unstaked:
    base = 128
else if total_stake == 0 or peer_stake > total_stake:
    base = 128
else:
    base = clamp(floor(binary64(peer_stake) / binary64(total_stake) * 99872)
                 + 128,
                 128, 512)

stream_limit = floor(base * clamp(rtt_ms,50,350) / 50)

The two integer stake values are converted separately to IEEE-754 binary64; the quotient and multiplication are binary64 round-to-nearest-ties-to-even, and conversion to u32 truncates toward zero before adding 128. The final multiply saturates in u32 before integer division.

The canonical active sender profile presents one Ed25519 end-entity certificate. Its DER certificate is 249 bytes: the fixed X.509 template has Ed25519 OID 1.3.101.112, serial 01 repeated eight times, issuer CN Solana node, validity 1970-01-01 through 4096-01-01, empty subject, a critical DNS SAN localhost, critical empty basic constraints, and a deliberately invalid 64-byte all-ff certificate signature. The only variable certificate bytes are the 32-byte Ed25519 SubjectPublicKeyInfo. The corresponding PKCS#8 private key is the 16-byte prefix 30 2e 02 01 00 30 05 06 03 2b 65 70 04 22 04 20 followed by the 32-byte Ed25519 secret scalar.

Informative table. Canonical dummy-certificate byte construction.

Byte rangeExact bytes
[0,100)30 81 f6 30 81 a9 a0 03 02 01 02 02 08 01 01 01 01 01 01 01 01 30 05 06 03 2b 65 70 30 16 31 14 30 12 06 03 55 04 03 0c 0b 53 6f 6c 61 6e 61 20 6e 6f 64 65 30 20 17 0d 37 30 30 31 30 31 30 30 30 30 30 30 5a 18 0f 34 30 39 36 30 31 30 31 30 30 30 30 30 30 5a 30 00 30 2a 30 05 06 03 2b 65 70 03 21 00
[100,132)the identity’s 32 raw Ed25519 public-key bytes
[132,249)a3 29 30 27 30 17 06 03 55 1d 11 01 01 ff 04 0d 30 0b 82 09 6c 6f 63 61 6c 68 6f 73 74 30 0c 06 03 55 1d 13 01 01 ff 04 02 30 00 30 05 06 03 2b 65 70 03 41 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

Certificate-chain, issuer, time, SAN, SNI and certificate-signature validation are deliberately not trust anchors. The TLS 1.3 CertificateVerify signature under the certificate public key is mandatory. A server extracts a peer identity for stake QoS only from a one-certificate chain whose SPKI is exactly 32 Ed25519 bytes; failure to extract it classifies the peer as unstaked rather than authenticating a transaction. A requester supplies SNI <decimal-IP>.<decimal-port>.sol; the responder does not match it to the SAN.

[SOL-WIR-0017] Both endpoints MUST verify TLS handshake signatures and MUST require client authentication at the handshake, while ignoring PKI chain, name, time, and dummy-certificate-signature validation as described above. A transport identity MUST NOT replace transaction signatures.

[SOL-WIR-0018] A requester MUST finish each transaction stream. EOF after 1..=4,096 bytes delivers one packet to the Chapter 7 decoder; reset, stop, timeout, zero bytes, 4,097 bytes, a second transaction in the stream, or a transaction split between streams discards that stream without an application response. A responder MAY close or reset throttled streams or connections; the sender then retries by opening a new stream or connection and MUST NOT infer transaction acceptance from transport completion.

Informative table. QUIC application close codes in the active liveness profile.

CodeUTF-8 reasonFirst use
1droppedan admitted connection-table entry is displaced
2disallowedpeer or connection classification is not admitted
4too_manythe applicable connection limit is exceeded
5invalid_streaman empty or oversized transaction stream is received

Repair and TVU use UDP in epoch E; their recognized QUIC socket keys do not select this TPU ALPN or transaction stream grammar. TPU, TPU-forward, and TPU-vote QUIC differ only in advertised endpoint and downstream transaction classification. TPU-vote additionally accepts the UDP form above.

6.6.1 Alpenglow votor QUIC datagrams

The ContactInfo key-13 endpoint carries Alpenglow votes and certificates. This service is independent of TPU transaction ingestion.

Normative table. Alpenglow transport profile.

ParameterActive value
QUIC versionversion 1
TLS versionTLS 1.3
TLS 1.3 cipher suitesAES-128-GCM-SHA256, AES-256-GCM-SHA384, and ChaCha20-Poly1305-SHA256
ALPNASCII alpenglow-v1
application carriageQUIC DATAGRAM only
bidirectional and unidirectional streamsdisabled; initial limits are zero
path MTU1,280 bytes; path-MTU discovery disabled
connection migrationdisabled
idle timeout5 seconds
keepalive interval2 seconds
0-RTTdisabled
peer identitynode-identity Ed25519 public key extracted from the mutually authenticated certificate
peer admissionidentity must belong to the admitted staked-node set for the relevant epoch window
inbound connections per admitted peerat most 2

Certificate construction and acceptance use the identity-certificate rules in Section 6.6. The TLS CertificateVerify signature binds possession of the advertised node identity. A certificate whose Ed25519 public key cannot be extracted, or whose identity is not in the admitted staked-node set, does not create an unstaked Alpenglow peer; the connection is rejected. Vote verification maps that node identity to its vote-account rank, effective stake, and BLS public key for the target slot’s epoch.

[SOL-WIR-0019] An Alpenglow endpoint MUST negotiate the transport profile above, MUST NOT accept consensus messages on a QUIC stream, and MUST NOT accept early data. It MUST discard a datagram that is truncated, has trailing bytes, has another shred version, has an unknown version or kind, or exceeds the negotiated datagram size. Transport receipt is not a vote: Chapter 15 signature, rank, and stake checks remain mandatory.

The datagram payload uses the ALPENGLOW-WIRE-E1072 codec in Section 15.2. QUIC connection counts, handshake-rate governors, send retry intervals, and queue capacities beyond the interoperability values above are local liveness policy rather than consensus or ledger validity rules.

6.7 Repair

Repair requests use a u32 little-endian top-level discriminant. Variants 0..=6 are obsolete unsigned forms and are rejected. Variant 7 is a Pong; variants 8..=11 begin with this signed header:

Normative table.

Byte rangeHeader field
[4,68)signature [64]
[68,100)sender public key [32]
[100,132)recipient public key [32]
[132,140)Unix timestamp in milliseconds, u64
[140,144)nonce, u32

Normative table.

Disc.RequestExact fields after headerTotal bytes
7Pongno header; from[32], hash[32], signature[64]132
8WindowIndexslot:u64, shred_index:u64160
9HighestWindowIndexslot:u64, shred_index:u64160
10Orphanslot:u64152
11AncestorHashesslot:u64152

[SOL-WIR-0020] The signature for variants 8..=11 MUST verify under the header sender over LE(discriminant,4) || bytes[68..end]; the signature field itself is omitted. The recipient MUST be the receiving identity and the timestamp MUST be within 600,000 milliseconds of current Unix time.

Ordinary shred responses are one raw Chapter 9 shred followed by the request nonce as u32 little-endian. Repair ping challenges use discriminant 0, then the 128-byte Ping layout from Section 6.4, for a total of 132 bytes.

Normative table.

Specialized responseDiscriminant and exact payload before trailing nonce
AncestorHashesResponse::HashesLE(0,4), u64 count, then at most 30 (slot:u64,hash[32]) records
AncestorHashesResponse::PingLE(1,4), Ping [128]

[SOL-WIR-0021] A repair response MUST end in the outstanding request’s nonce and match its request: WindowIndex requires the exact slot and index; HighestWindowIndex requires the exact slot and an index not below the request; Orphan permits at most 11 responses with shred slot not above the request; AncestorHashes permits at most 30 pairs. Every returned shred MUST pass Chapter 9 before use.

6.8 Turbine carriage

[SOL-WIR-0022] A TVU UDP datagram MUST carry exactly one raw Chapter 9 shred without an application header. Before reconstruction, the receiver MUST validate the shred’s version, byte layout, Merkle proof, and leader signature.

[SOL-WIR-0023] Forwarding MUST NOT alter bytes covered by the leader-signed Merkle root. A retransmitter signature, when present in its Chapter 9 trailer, is carried unchanged by subsequent forwarding and does not replace leader authentication.

6.8.1 Epoch-E Turbine topology

This subsection is the minimum network-liveness profile for a validator wire sender. Its permutation and forwarding requirements promote interoperable delivery, but a peer’s fanout choice is not ledger data and does not make an otherwise valid shred or block invalid.

For one shred identity (slot,index,type) and scheduled leader L, form the 32-byte seed SHA256(LE(slot,8) || type_u8 || LE(index,4) || L[32]), where type_u8 is 0xa5 for data and 0x5a for coding shreds. Initialize ChaCha8 with this seed, counter zero, stream zero, the RFC 8439 constants, and four column/diagonal double rounds per 64-byte block. The RNG exposes each block as consecutive little-endian u32 words; next_u64 concatenates the next two words with the first as the low half.

Construct the candidate node vector from every positive-stake identity in the slot’s epoch stake map, every current gossip identity with a valid key-10 TVU UDP address, and the local identity. Sort by descending (stake,pubkey) and deduplicate by pubkey, preferring the record with ContactInfo. Then traverse that order and retain only the first usable key-10 endpoint for each exact (UDP,socket-address) and only the first usable key-10 endpoint for each IP address. For a later duplicate, remove its endpoint: retain the identity if its stake is positive, but drop it when its stake is zero and it has no other active TVU endpoint. This address deduplication is part of the permutation input, not just a send-time filter. The leader is removed. Give node i weight equal to its epoch stake in lamports. To sample from total positive weight T, set zone=(T << leading_zeros(T))-1; repeatedly draw RNG x, compute p=x*T as u128, hi=p>>64, lo=p mod 2^64, and accept hi when lo<=zone. Select the least index whose cumulative remaining weight exceeds hi, remove that weight, and repeat. After positive weights are exhausted, select zero-weight indices uniformly by the same multiply-high method and swap-remove each selected index. This produces the per-shred permutation P.

Normative algorithm. Turbine fanout tree (F=200).

leader sends the shred to P[0]
for a node at permutation index i:
    offset = saturating_sub(i, 1) mod F
    anchor = i - offset
    step = 1 if i == 0 else F
    child indices are the first F in
        anchor*F + offset + 1, +step, +step, ...
        that are less than len(P)
parent(i):
    none when i == 0
    offset = (i-1) mod F
    q = floor((i-1)/F)
    base = q - saturating_sub(q,1) mod F
    parent index = base if base == 0 else base + offset

[SOL-WIR-0024] A leader MUST originate each valid shred to P[0]. A TVU participant receiving a newly accepted broadcast shred MUST forward it to the available key-10 UDP addresses at its child indices, in tree order, with at most 200 children and at most four hops. It MUST NOT retransmit a shred received as a repair response. Missing, stale, duplicate-IP, or invalid TVU addresses are skipped; repair under Section 6.7 is the fallback for the resulting holes.

[SOL-WIR-0025] Turbine membership is evaluated from the participant’s current authenticated gossip view, so two participants may omit different unreachable addresses without changing shred or block validity. The stake weights, seed, ChaCha8 word stream, sampling rule, and index-to-child mapping MUST NOT be replaced by an implementation-dependent shuffle.