hypercall/startup/
rsm_signer.rs1use 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}