Conversation
leafaar
reviewed
Aug 31, 2026
Comment on lines
+68
to
+83
| fn leader_lookahead( | ||
| &self, | ||
| leader_forward_lookahead: usize, | ||
| out: &mut [MaybeUninit<Pubkey>], | ||
| ) -> usize { | ||
| let schedule = self.share.read().unwrap(); | ||
| schedule[..leader_forward_lookahead].to_vec() | ||
|
|
||
| let it = schedule[..leader_forward_lookahead] | ||
| .iter() | ||
| .zip(out.iter_mut()); | ||
| let mut i = 0; | ||
| for (src, dst) in it { | ||
| dst.write(*src); | ||
| i += 1; | ||
| } | ||
| i |
Contributor
There was a problem hiding this comment.
i know you reutilize the same thing for tests so can you use the same function just make sure we don't change something here or there and forget something?
leafaar
approved these changes
Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Harden how the TPU sender's identity secret is held in memory
Motivation
The TPU sender's identity keypair — and every intermediate copy of it produced while loading, converting, and using it — previously lived in ordinary process memory: swappable to disk, and in some cases never zeroed after use. This PR closes that gap end-to-end, from reading a keypair off disk through to signing the QUIC/TLS handshake, without changing the wire formats or on-disk file format anything downstream depends on.
What changed
New
yellowstone_jet_tpu_client::identitymodule (crates/tpu-client/src/identity.rs), replacing direct use ofsolana_keypair::Keypairthroughout the sender:TpuIdentity— aPubkeyplus its derived QUIC client TLS credentials. Cheap to clone (Arcinternally, exposed via an explicitinsecure_clone()rather thanClone, to make duplication visible at call sites). Implementsquinn::crypto::ClientConfigdirectly, so it can be handed straight toquinn::ClientConfig::new().rustls::sign::SigningKey/Signer(MlockedSigningKey) that signs directly from a dedicated,mlocked buffer — bypassingrustls's standardwith_client_auth_cert→ringkey-loading path entirely, which would otherwise leave an unlocked, never-zeroized copy of the key parsed intoring's internal representation for the connection's whole lifetime.new_dummy_x509_certificate, writing the PKCS#8-encoded key directly into a buffer that'smlocked before any byte is copied into it (thesolana-tls-utilsversion returns an ordinary, unlockedVec).TpuEd25519SigningKeytrait, so it can build from a plainKeypair, a rawed25519_dalek::SigningKey, or aHardenedKeypair.HardenedKeypair— a keypair whose secret bytes aremlocked from the moment they exist and zeroized on drop. Reads (read_from_reader/read_from_file) go straight into a growable, always-locked scratch buffer instead ofsolana_keypair::read_keypair's plainString;TryFrom<&[u8]>verifies the public half actually corresponds to the secret half (rejecting a corrupted/tampered keypair) using the same checked25519-dalekuses internally.GrowableHardenedBuffer— backsHardenedKeypair's file reads. Grows by allocating a new, larger, locked buffer, copying existing bytes over, and zeroizing the old allocation before dropping it — so a resize never leaves a byte outside locked memory.TpuSenderIdentityUpdater::current_identity()— a newArc<ArcSwap<Pubkey>>shared between the driver and the updater handle, so callers can cheaply read the driver's current identity pubkey without round-tripping through the command-and-control channel (replaces the oldtokio::sync::watch-based observer plumbing).apps/jet:JetIdentitySyncGroup/JetIdentitySyncMember(the old fan-out-to-multiple-members abstraction) are removed;TpuSenderIdentityUpdaternow implementsJetIdentityUpdaterdirectly, since it was the only real member.setIdentity,setIdentityFromBytes,resetIdentity) now build aHardenedKeypairinstead of a plainKeypair.ConfigJet::prometheus,spawn_push_prometheus_metrics) — it depended on the oldwatch::Receiver<Pubkey>identity-observer plumbing that no longer exists. Flagging this explicitly since it's a user-facing config removal, not an internal refactor — worth a second look if anyone relies on it.current_identity()on a fixed interval (15s) instead of being woken by the identity-observer channel.Known, accepted residual exposure
Both
MlockedSigner::sign()andHardenedKeypair's consistency check construct a transiented25519_dalek::SigningKeyon the stack to do the actual crypto operation —ed25519-dalek's API has no way to sign/derive using externally-owned, already-locked memory. That transient value self-zeroizes on drop (ZeroizeOnDrop) and is gone within a handful of instructions; it's a materially smaller exposure than the previous status quo (heap-resident insidering, for the connection's entire lifetime, never zeroized), but it isn't literally zero. Closing it further would mean hand-rolling Ed25519 point arithmetic ourselves — a deliberate call not to make, for the same "don't roll your own crypto" reasoning that ruled out a customquinn_proto::crypto::ClientConfigearlier in this effort.Testing
crates/tpu-client/src/identity.rs: 12 new unit tests, including:new_dummy_x509_certificateproduces byte-identical output tosolana_tls_utils::new_dummy_x509_certificate.MlockedSigningKeyproduces byte-identical Ed25519 signatures toring's own EdDSA signer for the same key/message (signing is deterministic per RFC 8032, so this is a strong equivalence check, not just "it doesn't crash").HardenedKeypair::read_from_reader/read_from_fileparse the same JSON format assolana_keypair::read_keypairand recover byte-identical keys, including with input larger than the initial buffer guess (forces a grow).HardenedKeypair::try_fromaccepts valid keypair bytes and rejects a tampered public half.test_quic_gateway,test_yellowstone_tpu_sender,test_rpc_admin) updated for the new types and passing, including real QUIC handshake tests that exercise the new signing path end-to-end (not just unit-level).cargo check,cargo clippy --all-targets --all-features -- -D warnings,cargo fmt --check, and all tests green.