Skip to main content

GrpcSource

Struct GrpcSource 

Source
pub struct GrpcSource { /* private fields */ }
Expand description

Mirror.LogHead / Mirror.LogFetch against the primary, signed with the twin’s own key by the one client signer (gunnar_grpc::client_signer).

Implementations§

Source§

impl GrpcSource

Source

pub fn connect(at: &str, signer: PrivateKey, root: &Path) -> Result<Self>

A source for the primary at at (host:port), as signer, for the twin whose data root is root.

root is not decoration: it is where the key this primary answers with is pinned on the first poll and read back on every one after (PRIMARY_KEY). A source that could not say which twin it belongs to could not enforce anything, so it is a parameter rather than an optional builder — there is no such thing here as a follower without a data root.

§Mutual TLS whenever the primary is another machine

The root pinning above answers who and which history — the primary’s sshsig over gunnar-log-head-v1, TOFU-pinned, and the log_id carried on every poll. Those are settled and nothing here re-solves them. What they do not answer is confidentiality: this channel carries the primary’s transaction log, and seq 1 of that log is the KEK. A signed answer proves the bytes are genuine; it does not stop anyone on the path from reading every sealed secret in the estate as they go by. Until 2026-09-21 they went by in clear text.

So the channel is encrypted too, and the rule has no flag:

  • a loopback primary (the test harness, a solo box following itself) stays plaintext — the bytes never reach a wire;
  • any other primary needs ClientTrust from GUNNAR_CLIENT_CERT/GUNNAR_CLIENT_KEY/GUNNAR_CA, and a follower without it REFUSES to start rather than following in the clear. plain_channel refuses the same target for the same reason; this refusal exists so the twin says which three files are missing instead of failing at the transport with the address alone.

The channel is built once and reused for every poll: connect_lazy hands back a Channel that opens its HTTP/2 connection on first use and keeps it, so the 2-second poll loop pays one TLS handshake per connection and not one per poll. Measured at ~0.5 ms, a handshake per poll would still be under a tenth of a percent of the poll budget — but it would also be far more than the per-poll signature verification the liveness half already does, and there is no reason to spend it.

Source§

impl GrpcSource

Source

pub fn with_reporter( self, report: impl Fn() -> TwinReport + Send + Sync + 'static, ) -> Self

Carry report() on every LogHead (the dual heartbeat).

Trait Implementations§

Source§

impl LogSource for GrpcSource

Source§

fn head_and_identity(&self) -> Result<(u64, u64, Option<String>)>

The numbers AND the name, out of one answer — see LogSource::head_and_identity. This is the only override, because this is the only source that reached across a wire to a box that could be anything.

Source§

fn fetch_into( &self, from: u64, to: u64, max_bytes: u64, into: &mut dyn FrameSink, ) -> Result<()>

Each chunk into into as it arrives (twin-streaming, 2026-10-06). This used to glue every entry’s pieces back into one Vec — a 256 MiB pack was 256 MiB on the twin before a byte of it reached the disk, and a 1 GiB twin under such packs went silent. Now the twin holds the piece in hand; HTTP/2 flow control holds the primary back while the twin writes.

Source§

fn alternatives(&self) -> bool

Does the primary have another address to re-dial when this one keeps failing? (The tunnel’s and the utility network’s.) In-process sources have none.
Source§

fn head(&self) -> Result<(u64, u64)>

(first_seq, head_seq) on the primary.
Source§

fn head_identity_generation(&self) -> Result<(u64, u64, Option<String>, u64)>

LogSource::head_and_identity and the source’s GENERATION (LOG_GENERATION), out of the same answer. 0 = none claimed: every in-process source, and a primary never restored or older than the field.
Source§

fn fetch(&self, from: u64, to: u64, max_bytes: u64) -> Result<Vec<Entry>>

Entries from..=to, at most about max_bytes of blob, never fewer than one when from <= to.
Source§

fn snapshot(&self, into: &mut Stager) -> Result<bool>

A consistent snapshot of the source’s stores, for a twin behind a retention hole, fed piece by piece into the twin’s staging area as it arrives. false = this source cannot make one (a bare log): the twin stops with snapshot-needed.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more