Skip to main content

aion_server/namespace/
mint_forward.rs

1//! The production [`MintForwarder`]: an already-authorized namespace mint
2//! shipped to the registry shard's owner over the R-3 gRPC forwarder.
3//!
4//! Reuses the forwarder the routing edge already dials signals/queries/cancels
5//! and steered starts through, so a forwarded mint gets the SAME caller-metadata
6//! copy and the SAME hop stamp (loop prevention) as every other forwarded
7//! request — no second transport, no second set of rules.
8//!
9//! Dormant without a `[store.cluster]` section: with no cluster there is
10//! nothing to forward to.
11
12use std::net::SocketAddr;
13use std::sync::Arc;
14
15use async_trait::async_trait;
16
17use aion_proto::generated;
18use aion_store::NamespaceOrigin;
19
20use super::route::{ForwardMintError, MintCredentials, MintForwarder, encode_mint_origin};
21use crate::routing::{ForwardReply, ForwardRequest, RequestForwarder, owner_refusal};
22
23/// Ships mints over the R-3 gRPC request forwarder.
24#[derive(Clone)]
25pub struct GrpcMintForwarder {
26    forwarder: Arc<dyn RequestForwarder>,
27}
28
29impl GrpcMintForwarder {
30    /// Wrap the boot's shared request forwarder.
31    #[must_use]
32    pub const fn new(forwarder: Arc<dyn RequestForwarder>) -> Self {
33        Self { forwarder }
34    }
35}
36
37/// Classify a forward failure WITHOUT reading message text.
38///
39/// The server encodes its typed `WireError` into the status details on every
40/// refusal it authors; a transport failure the forwarder itself synthesises (a
41/// failed dial) carries no details. So "did the owner answer?" is a structural
42/// question about the payload, answered structurally — by
43/// [`crate::routing::owner_refusal`], the one place every forwarding surface
44/// asks it.
45fn classify(status: &tonic::Status) -> ForwardMintError {
46    owner_refusal(status).map_or_else(
47        || ForwardMintError::Unreachable(status.code().to_string()),
48        ForwardMintError::Refused,
49    )
50}
51
52#[async_trait]
53impl MintForwarder for GrpcMintForwarder {
54    async fn forward_mint(
55        &self,
56        target: SocketAddr,
57        credentials: &MintCredentials,
58        namespaces: &[String],
59        origin: NamespaceOrigin,
60    ) -> Result<(), ForwardMintError> {
61        let request = generated::MintNamespaceRequest {
62            namespaces: namespaces.to_vec(),
63            origin: encode_mint_origin(origin),
64        };
65        match self
66            .forwarder
67            .forward(
68                target,
69                credentials.to_grpc_metadata(),
70                ForwardRequest::MintNamespace(request),
71            )
72            .await
73        {
74            Ok(ForwardReply::MintNamespace(_)) => Ok(()),
75            Ok(_) => Err(ForwardMintError::Unreachable(
76                "the forwarder returned a reply for a different request".to_owned(),
77            )),
78            Err(status) => Err(classify(&status)),
79        }
80    }
81}