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
impl GrpcSource
Sourcepub fn connect(at: &str, signer: PrivateKey, root: &Path) -> Result<Self>
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
ClientTrustfromGUNNAR_CLIENT_CERT/GUNNAR_CLIENT_KEY/GUNNAR_CA, and a follower without it REFUSES to start rather than following in the clear.plain_channelrefuses 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
impl GrpcSource
Sourcepub fn with_reporter(
self,
report: impl Fn() -> TwinReport + Send + Sync + 'static,
) -> Self
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
impl LogSource for GrpcSource
Source§fn head_and_identity(&self) -> Result<(u64, u64, Option<String>)>
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<()>
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
fn alternatives(&self) -> bool
Source§fn head_identity_generation(&self) -> Result<(u64, u64, Option<String>, u64)>
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.Auto Trait Implementations§
impl !Freeze for GrpcSource
impl !RefUnwindSafe for GrpcSource
impl !UnwindSafe for GrpcSource
impl Send for GrpcSource
impl Sync for GrpcSource
impl Unpin for GrpcSource
impl UnsafeUnpin for GrpcSource
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request