1use crate::constants::{
2 ACTION_ITEM_CHECK_ADDRESS, ACTION_ITEM_CHECK_BALANCE, CHECKED_ADDRESS, IS_BALANCE_CHECKED,
3 PROVIDE_PUBLIC_KEY_ACTION_RESULT,
4};
5use crate::helpers::hcl::visit_optional_untyped_attribute;
6use crate::types::stores::ValueStore;
7use futures::future;
8use hcl_edit::{expr::Expression, structure::Block, Span};
9use std::{collections::HashMap, future::Future, pin::Pin};
10
11use super::commands::ConstructInstance;
12use super::{
13 commands::{
14 CommandExecutionResult, CommandInput, CommandInputsEvaluationResult, CommandOutput,
15 },
16 diagnostics::Diagnostic,
17 frontend::{
18 ActionItemRequest, ActionItemResponse, ActionItemResponseType, Actions, BlockEvent,
19 },
20 types::{ObjectProperty, RunbookSupervisionContext, Type, Value},
21 ConstructDid, PackageId,
22};
23use super::{AuthorizationContext, Did, EvaluatableInput};
24
25#[derive(Debug, Clone)]
26pub struct SignersState {
27 pub store: HashMap<ConstructDid, ValueStore>,
28}
29
30impl SignersState {
31 pub fn new() -> SignersState {
32 SignersState { store: HashMap::new() }
33 }
34
35 pub fn get_first_signer(&self) -> Option<ValueStore> {
36 self.store.values().next().cloned()
37 }
38
39 pub fn get_signer_state_mut(&mut self, signer_did: &ConstructDid) -> Option<&mut ValueStore> {
40 self.store.get_mut(signer_did)
41 }
42
43 pub fn get_signer_state(&self, signer_did: &ConstructDid) -> Option<&ValueStore> {
44 self.store.get(signer_did)
45 }
46
47 pub fn pop_signer_state(&mut self, signer_did: &ConstructDid) -> Option<ValueStore> {
48 self.store.remove(signer_did)
49 }
50
51 pub fn push_signer_state(&mut self, signer_state: ValueStore) {
52 self.store.insert(ConstructDid(signer_state.uuid.clone()), signer_state);
53 }
54
55 pub fn create_new_signer(&mut self, signer_did: &ConstructDid, signer_name: &str) {
72 if !self.store.contains_key(&signer_did) {
73 self.store
74 .insert(signer_did.clone(), ValueStore::new(signer_name, &signer_did.value()));
75 }
76 }
77}
78pub type SignerActionOk = (SignersState, ValueStore, CommandExecutionResult);
79pub type SignerActionErr = (SignersState, ValueStore, Diagnostic);
80pub type SignerActivateFutureResult = Result<
81 Pin<Box<dyn Future<Output = Result<SignerActionOk, SignerActionErr>> + Send>>,
82 SignerActionErr,
83>;
84
85pub fn consolidate_signer_activate_result(
86 res: Result<SignerActionOk, SignerActionErr>,
87 block_span: Option<std::ops::Range<usize>>,
88) -> Result<(SignersState, CommandExecutionResult), (SignersState, Diagnostic)> {
89 match res {
90 Ok((mut signers, signer_state, result)) => {
91 signers.push_signer_state(signer_state);
92 Ok((signers, result))
93 }
94 Err((mut signers, signer_state, diag)) => {
95 signers.push_signer_state(signer_state);
96 Err((signers, diag.set_span_range(block_span)))
97 }
98 }
99}
100pub async fn consolidate_signer_activate_future_result(
101 future: SignerActivateFutureResult,
102 block_span: Option<std::ops::Range<usize>>,
103) -> Result<
104 Result<(SignersState, CommandExecutionResult), (SignersState, Diagnostic)>,
105 (SignersState, Diagnostic),
106> {
107 match future {
108 Ok(res) => Ok(consolidate_signer_activate_result(res.await, block_span)),
109 Err((mut signers, signer_state, diag)) => {
110 signers.push_signer_state(signer_state);
111 Err((signers, diag.set_span_range(block_span)))
112 }
113 }
114}
115
116pub type SignerActivateClosure = Box<
117 fn(
118 &ConstructDid,
119 &SignerSpecification,
120 &ValueStore,
121 ValueStore,
122 SignersState,
123 &HashMap<ConstructDid, SignerInstance>,
124 &channel::Sender<BlockEvent>,
125 ) -> SignerActivateFutureResult,
126>;
127
128pub type SignerSignFutureResult = Result<
129 Pin<Box<dyn Future<Output = Result<SignerActionOk, SignerActionErr>> + Send>>,
130 SignerActionErr,
131>;
132
133pub type SignerSignClosure = Box<
134 fn(
135 &ConstructDid,
136 &str,
137 &Value,
138 &SignerSpecification,
139 &ValueStore,
140 ValueStore,
141 SignersState,
142 &HashMap<ConstructDid, SignerInstance>,
143 ) -> SignerSignFutureResult,
144>;
145
146pub type SignerCheckActivabilityClosure = fn(
147 &ConstructDid,
148 &str,
149 &SignerSpecification,
150 &ValueStore,
151 ValueStore,
152 SignersState,
153 &HashMap<ConstructDid, SignerInstance>,
154 &RunbookSupervisionContext,
155 &AuthorizationContext,
156 bool,
157 bool,
158) -> SignerActionsFutureResult;
159
160pub type SignerActionsFutureResult = Result<
161 Pin<Box<dyn Future<Output = Result<CheckSignabilityOk, SignerActionErr>> + Send>>,
162 SignerActionErr,
163>;
164
165pub type PrepareSignedNestedExecutionResult = Result<
166 Pin<Box<dyn Future<Output = Result<PrepareNestedExecutionOk, SignerActionErr>> + Send>>,
167 SignerActionErr,
168>;
169pub type PrepareNestedExecutionOk = (SignersState, ValueStore, Vec<(ConstructDid, ValueStore)>);
170
171pub type SignerCheckInstantiabilityClosure =
172 fn(&SignerSpecification, Vec<Type>) -> Result<Type, Diagnostic>;
173
174pub type CheckSignabilityOk = (SignersState, ValueStore, Actions);
175
176pub type SignerCheckSignabilityClosure = fn(
177 &ConstructDid,
178 &str,
179 &Option<String>,
180 &Option<String>,
181 &Option<String>,
182 &Value,
183 &SignerSpecification,
184 &ValueStore,
185 ValueStore,
186 SignersState,
187 &HashMap<ConstructDid, SignerInstance>,
188 &RunbookSupervisionContext,
189 &AuthorizationContext,
190) -> Result<CheckSignabilityOk, SignerActionErr>;
191
192pub type SignerOperationFutureResult = Result<
193 Pin<Box<dyn Future<Output = Result<SignerActionOk, SignerActionErr>> + Send>>,
194 SignerActionErr,
195>;
196
197pub fn return_synchronous<T>(
198 res: T,
199) -> Result<Pin<Box<dyn Future<Output = Result<T, SignerActionErr>> + Send>>, SignerActionErr>
200where
201 T: std::marker::Send + 'static,
202{
203 Ok(Box::pin(future::ready(Ok(res))))
204}
205
206pub fn return_synchronous_actions(
207 res: Result<CheckSignabilityOk, SignerActionErr>,
208) -> SignerActionsFutureResult {
209 Ok(Box::pin(future::ready(res)))
210}
211
212pub fn return_synchronous_result(
213 res: Result<SignerActionOk, SignerActionErr>,
214) -> SignerOperationFutureResult {
215 Ok(Box::pin(future::ready(res)))
216}
217
218pub fn return_synchronous_ok(
219 signers: SignersState,
220 signer_state: ValueStore,
221 res: CommandExecutionResult,
222) -> SignerOperationFutureResult {
223 return_synchronous_result(Ok((signers, signer_state, res)))
224}
225
226pub fn return_synchronous_err(
227 signers: SignersState,
228 signer_state: ValueStore,
229 diag: Diagnostic,
230) -> SignerOperationFutureResult {
231 return_synchronous_result(Err((signers, signer_state, diag)))
232}
233
234pub fn consolidate_signer_result(
235 res: Result<CheckSignabilityOk, SignerActionErr>,
236 block_span: Option<std::ops::Range<usize>>,
237) -> Result<(SignersState, Actions), (SignersState, Diagnostic)> {
238 match res {
239 Ok((mut signers, signer_state, actions)) => {
240 signers.push_signer_state(signer_state);
241 Ok((signers, actions))
242 }
243 Err((mut signers, signer_state, diag)) => {
244 signers.push_signer_state(signer_state);
245 Err((signers, diag.set_span_range(block_span)))
246 }
247 }
248}
249pub async fn consolidate_signer_future_result(
250 future: SignerActionsFutureResult,
251 block_span: Option<std::ops::Range<usize>>,
252) -> Result<(SignersState, Actions), (SignersState, Diagnostic)> {
253 match future {
254 Ok(res) => match res.await {
255 Ok((mut signers, signer_state, actions)) => {
256 signers.push_signer_state(signer_state);
257 Ok((signers, actions))
258 }
259 Err((mut signers, signer_state, diag)) => {
260 signers.push_signer_state(signer_state);
261 Err((signers, diag.set_span_range(block_span)))
262 }
263 },
264 Err((mut signers, signer_state, diag)) => {
265 signers.push_signer_state(signer_state);
266 Err((signers, diag.set_span_range(block_span)))
267 }
268 }
269}
270
271pub async fn consolidate_nested_execution_result(
272 future: PrepareSignedNestedExecutionResult,
273 block_span: Option<std::ops::Range<usize>>,
274) -> Result<(SignersState, Vec<(ConstructDid, ValueStore)>), (SignersState, Diagnostic)> {
275 match future {
276 Ok(res) => match res.await {
277 Ok((mut signers, signer_state, res)) => {
278 signers.push_signer_state(signer_state);
279 Ok((signers, res))
280 }
281 Err((mut signers, signer_state, diag)) => {
282 signers.push_signer_state(signer_state);
283 Err((signers, diag.set_span_range(block_span)))
284 }
285 },
286 Err((mut signers, signer_state, diag)) => {
287 signers.push_signer_state(signer_state);
288 Err((signers, diag.set_span_range(block_span)))
289 }
290 }
291}
292
293#[derive(Debug, Clone)]
294pub struct SignerSpecification {
295 pub name: String,
296 pub matcher: String,
297 pub documentation: String,
298 pub requires_interaction: bool,
299 pub example: String,
300 pub default_inputs: Vec<CommandInput>,
301 pub inputs: Vec<CommandInput>,
302 pub outputs: Vec<CommandOutput>,
303 pub check_instantiability: SignerCheckInstantiabilityClosure,
304 pub check_activability: SignerCheckActivabilityClosure,
305 pub activate: SignerActivateClosure,
306 pub check_signability: SignerCheckSignabilityClosure,
307 pub sign: SignerSignClosure,
308 pub force_sequential_signing: bool,
309}
310
311#[derive(Debug, Clone)]
312pub struct SignerInstance {
313 pub specification: SignerSpecification,
314 pub name: String,
315 pub block: Block,
316 pub package_id: PackageId,
317 pub namespace: String,
318}
319
320impl SignerInstance {
321 pub fn compute_fingerprint(&self, evaluated_inputs: &CommandInputsEvaluationResult) -> Did {
322 let mut comps = vec![];
323 for input in self.specification.inputs.iter() {
324 let Some(value) = evaluated_inputs.inputs.get_value(&input.name) else { continue };
325 if input.sensitive {
326 comps.push(value.to_be_bytes());
327 }
328 }
329 Did::from_components(comps)
330 }
331
332 pub fn get_expression_from_input(&self, input_name: &str) -> Option<Expression> {
334 visit_optional_untyped_attribute(&input_name, &self.block)
335 }
336
337 pub fn get_group(&self) -> String {
338 let Some(group) = self.block.body.get_attribute("group") else {
339 return format!("{} Review", self.specification.name.to_string());
340 };
341 group.value.to_string()
342 }
343
344 pub fn get_expression_from_object_property(
345 &self,
346 input_name: &str,
347 prop: &ObjectProperty,
348 ) -> Option<Expression> {
349 let object = self.block.body.get_blocks(&input_name).next();
350 match object {
351 Some(block) => {
352 let expr_res = visit_optional_untyped_attribute(&prop.name, &block);
353 match expr_res {
354 Some(expression) => Some(expression),
355 None => None,
356 }
357 }
358 None => None,
359 }
360 }
361
362 pub async fn check_activability(
363 &self,
364 construct_did: &ConstructDid,
365 evaluated_inputs: &CommandInputsEvaluationResult,
366 mut signers: SignersState,
367 signers_instances: &HashMap<ConstructDid, SignerInstance>,
368 action_item_requests: &Option<&Vec<&mut ActionItemRequest>>,
369 action_item_responses: &Option<&Vec<ActionItemResponse>>,
370 supervision_context: &RunbookSupervisionContext,
371 authorization_context: &AuthorizationContext,
372 is_balance_check_required: bool,
373 is_public_key_required: bool,
374 ) -> Result<(SignersState, Actions), (SignersState, Diagnostic)> {
375 let mut values = ValueStore::new(&self.name, &construct_did.value())
376 .with_defaults(&evaluated_inputs.inputs.defaults)
377 .with_inputs(&evaluated_inputs.inputs.inputs)
378 .check(&self.name, &self.specification.inputs)
379 .map_err(|e| (signers.clone(), e))?;
380
381 match action_item_responses {
382 Some(responses) => {
383 for ActionItemResponse { payload, action_item_id } in responses.iter() {
384 match payload {
385 ActionItemResponseType::ProvidePublicKey(update) => {
386 values.insert(
387 PROVIDE_PUBLIC_KEY_ACTION_RESULT,
388 Value::string(update.public_key.clone()),
389 );
390 }
391 ActionItemResponseType::ReviewInput(response) => {
392 let request = action_item_requests
393 .map(|requests| requests.iter().find(|r| r.id.eq(&action_item_id)));
394
395 if let Some(Some(request)) = request {
396 if let Some(signer_did) = &request.construct_did {
397 let mut signer_state =
398 signers.pop_signer_state(signer_did).unwrap();
399 if request.internal_key == ACTION_ITEM_CHECK_ADDRESS {
400 if response.value_checked {
401 let data = request
402 .action_type
403 .as_review_input()
404 .expect("review input action item");
405 signer_state.insert(
406 CHECKED_ADDRESS,
407 Value::string(data.value.to_string()),
408 );
409 }
410 } else if request.internal_key == ACTION_ITEM_CHECK_BALANCE {
411 signer_state.insert(
412 IS_BALANCE_CHECKED,
413 Value::bool(response.value_checked),
414 );
415 }
416 signers.push_signer_state(signer_state);
417 }
418 }
419 }
420
421 _ => {}
422 }
423 }
424 }
425 None => {}
426 }
427
428 let signer_state = signers.pop_signer_state(construct_did).unwrap();
429 let spec = &self.specification;
430 let res = (spec.check_activability)(
431 &construct_did,
432 &self.name,
433 &self.specification,
434 &values,
435 signer_state,
436 signers,
437 signers_instances,
438 &supervision_context,
439 &authorization_context,
440 is_balance_check_required,
441 is_public_key_required,
442 );
443
444 consolidate_signer_future_result(res, self.block.span()).await
445 }
446
447 pub async fn perform_activation(
448 &self,
449 construct_did: &ConstructDid,
450 evaluated_inputs: &CommandInputsEvaluationResult,
451 mut signers: SignersState,
452 signers_instances: &HashMap<ConstructDid, SignerInstance>,
453 progress_tx: &channel::Sender<BlockEvent>,
454 ) -> Result<(SignersState, CommandExecutionResult), (SignersState, Diagnostic)> {
455 let values = ValueStore::new(&self.name, &construct_did.value())
456 .with_defaults(&evaluated_inputs.inputs.defaults)
457 .with_inputs(&evaluated_inputs.inputs.inputs);
458
459 let signer_state = signers.pop_signer_state(construct_did).unwrap();
460 let future = (&self.specification.activate)(
461 &construct_did,
462 &self.specification,
463 &values,
464 signer_state,
465 signers,
466 signers_instances,
467 progress_tx,
468 );
469 consolidate_signer_activate_future_result(future, self.block.span()).await?
470 }
471}
472
473impl ConstructInstance for SignerInstance {
474 fn block(&self) -> &Block {
475 &self.block
476 }
477 fn inputs(&self) -> Vec<Box<dyn EvaluatableInput>> {
478 self.specification
479 .inputs
480 .iter()
481 .chain(&self.specification.default_inputs)
482 .map(|input| Box::new(input.clone()) as Box<dyn EvaluatableInput>)
483 .collect()
484 }
485}
486
487pub trait SignerImplementation {
488 fn check_instantiability(
489 _ctx: &SignerSpecification,
490 _args: Vec<Type>,
491 ) -> Result<Type, Diagnostic>;
492
493 fn check_activability(
494 _construct_id: &ConstructDid,
495 _instance_name: &str,
496 _spec: &SignerSpecification,
497 _values: &ValueStore,
498 _signer_state: ValueStore,
499 _signers: SignersState,
500 _signers_instances: &HashMap<ConstructDid, SignerInstance>,
501 _supervision_context: &RunbookSupervisionContext,
502 _authorization_context: &AuthorizationContext,
503 _is_balance_check_required: bool,
504 _is_public_key_required: bool,
505 ) -> SignerActionsFutureResult {
506 unimplemented!()
507 }
508
509 fn activate(
510 _construct_id: &ConstructDid,
511 _spec: &SignerSpecification,
512 _values: &ValueStore,
513 _signer_state: ValueStore,
514 _signers: SignersState,
515 _signers_instances: &HashMap<ConstructDid, SignerInstance>,
516 _progress_tx: &channel::Sender<BlockEvent>,
517 ) -> SignerActivateFutureResult {
518 unimplemented!()
519 }
520
521 fn check_signability(
522 _caller_uuid: &ConstructDid,
523 _title: &str,
524 _description: &Option<String>,
525 _meta_description: &Option<String>,
526 _markdown: &Option<String>,
527 _payload: &Value,
528 _spec: &SignerSpecification,
529 _values: &ValueStore,
530 _signer_state: ValueStore,
531 _signers: SignersState,
532 _signers_instances: &HashMap<ConstructDid, SignerInstance>,
533 _supervision_context: &RunbookSupervisionContext,
534 _auth_ctx: &AuthorizationContext,
535 ) -> Result<CheckSignabilityOk, SignerActionErr> {
536 unimplemented!()
537 }
538
539 fn sign(
540 _caller_uuid: &ConstructDid,
541 _title: &str,
542 _payload: &Value,
543 _spec: &SignerSpecification,
544 _values: &ValueStore,
545 _signer_state: ValueStore,
546 _signers: SignersState,
547 _signers_instances: &HashMap<ConstructDid, SignerInstance>,
548 ) -> SignerSignFutureResult {
549 unimplemented!()
550 }
551}