Skip to main content

hypercall/startup/
rsm_signer.rs

1//! Startup wiring for RSM signer services.
2
3use std::sync::Arc;
4
5use hypercall_types::WalletAddress;
6use tokio::sync::OnceCell;
7
8use crate::db_handler::DbAuthConfig;
9
10pub struct StandbyRsmSignerService {
11    standby_controller: crate::runtime::standby::StandbyController,
12    config: crate::backend_config::BackendConfig,
13    secrets: crate::backend_config::BackendSecrets,
14    db_auth: DbAuthConfig,
15    chain_id: u64,
16    inner: OnceCell<Arc<dyn hypercall_signer::RsmSigner>>,
17}
18
19impl StandbyRsmSignerService {
20    pub fn new(
21        standby_controller: crate::runtime::standby::StandbyController,
22        config: crate::backend_config::BackendConfig,
23        secrets: crate::backend_config::BackendSecrets,
24        db_auth: DbAuthConfig,
25        chain_id: u64,
26    ) -> Self {
27        Self {
28            standby_controller,
29            config,
30            secrets,
31            db_auth,
32            chain_id,
33            inner: OnceCell::new(),
34        }
35    }
36
37    async fn inner(
38        &self,
39    ) -> Result<Arc<dyn hypercall_signer::RsmSigner>, hypercall_signer::RsmSignerError> {
40        if self.standby_controller.is_standby().await {
41            return Err(hypercall_signer::RsmSignerError::SigningFailed(
42                "RSM signer is deferred while standby mode is active".to_string(),
43            ));
44        }
45
46        self.inner
47            .get_or_try_init(|| async {
48                let diesel_db = Arc::new(
49                    hypercall_db_diesel::DieselDb::new_with_auth(self.db_auth.clone(), 4)
50                        .await
51                        .map_err(|error| {
52                            hypercall_signer::RsmSignerError::PersistenceFailed(format!(
53                                "failed to initialize RSM signer DieselDb after standby promotion: {}",
54                                error
55                            ))
56                        })?,
57                );
58                build_rsm_signer_service(&self.config, &self.secrets, diesel_db, self.chain_id)
59                    .await
60            })
61            .await
62            .cloned()
63    }
64}
65
66#[async_trait::async_trait]
67impl hypercall_signer::RsmSigner for StandbyRsmSignerService {
68    fn signer_address(&self) -> WalletAddress {
69        self.inner
70            .get()
71            .expect("standby RSM signer address unavailable before initialization")
72            .signer_address()
73    }
74
75    async fn sign(
76        &self,
77        request_id: &str,
78        account: &WalletAddress,
79        action: &[u8],
80    ) -> Result<hypercall_signer::SignedDirective, hypercall_signer::RsmSignerError> {
81        self.inner().await?.sign(request_id, account, action).await
82    }
83
84    async fn sign_preallocated(
85        &self,
86        request_id: &str,
87        account: &WalletAddress,
88        action: &[u8],
89        nonce: u64,
90    ) -> Result<hypercall_signer::SignedDirective, hypercall_signer::RsmSignerError> {
91        self.inner()
92            .await?
93            .sign_preallocated(request_id, account, action, nonce)
94            .await
95    }
96
97    async fn status(
98        &self,
99    ) -> Result<hypercall_signer::RsmSignerStatus, hypercall_signer::RsmSignerError> {
100        self.inner().await?.status().await
101    }
102
103    async fn is_nonce_used(&self, nonce: u64) -> Result<bool, hypercall_signer::RsmSignerError> {
104        self.inner().await?.is_nonce_used(nonce).await
105    }
106}
107
108pub async fn build_rsm_signer_service(
109    config: &crate::backend_config::BackendConfig,
110    secrets: &crate::backend_config::BackendSecrets,
111    diesel_handler: Arc<hypercall_db_diesel::DieselDb>,
112    chain_id: u64,
113) -> Result<Arc<dyn hypercall_signer::RsmSigner>, hypercall_signer::RsmSignerError> {
114    match config.rsm_signer.provider {
115        crate::backend_config::RsmSignerProvider::AwsKms => {
116            build_aws_kms_rsm_signer_service(
117                &config.rsm_signer.aws_kms_key_id,
118                &config.transaction_submitter.rpc_url,
119                &config.contracts.exchange_contract_address,
120                diesel_handler,
121                chain_id,
122            )
123            .await
124        }
125        crate::backend_config::RsmSignerProvider::Local => {
126            let private_key = secrets
127                .transaction_submitter_private_key
128                .as_deref()
129                .ok_or_else(|| {
130                    hypercall_signer::RsmSignerError::SigningFailed(
131                        "local RSM signer requires TRANSACTION_SUBMITTER_PRIVATE_KEY".to_string(),
132                    )
133                })?;
134            Ok(Arc::new(
135                hypercall_signer_local::LocalRsmSignerService::new(
136                    private_key,
137                    &config.transaction_submitter.rpc_url,
138                    &config.contracts.exchange_contract_address,
139                    chain_id,
140                    diesel_handler,
141                )
142                .await?,
143            ) as Arc<dyn hypercall_signer::RsmSigner>)
144        }
145    }
146}
147
148#[cfg(feature = "aws")]
149async fn build_aws_kms_rsm_signer_service(
150    key_id: &str,
151    rpc_url: &str,
152    exchange_contract_address: &str,
153    diesel_handler: Arc<hypercall_db_diesel::DieselDb>,
154    chain_id: u64,
155) -> Result<Arc<dyn hypercall_signer::RsmSigner>, hypercall_signer::RsmSignerError> {
156    hypercall_signer_aws::build_aws_kms_rsm_signer_service(
157        key_id,
158        rpc_url,
159        exchange_contract_address,
160        chain_id,
161        diesel_handler,
162    )
163    .await
164}
165
166#[cfg(not(feature = "aws"))]
167async fn build_aws_kms_rsm_signer_service(
168    _key_id: &str,
169    _rpc_url: &str,
170    _exchange_contract_address: &str,
171    _diesel_handler: Arc<hypercall_db_diesel::DieselDb>,
172    _chain_id: u64,
173) -> Result<Arc<dyn hypercall_signer::RsmSigner>, hypercall_signer::RsmSignerError> {
174    Err(hypercall_signer::RsmSignerError::SigningFailed(
175        "AWS KMS RSM signer requires building hypercall with the aws feature".to_string(),
176    ))
177}