Skip to main content

txtx_addon_kit/types/
signers.rs

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 get_mining_spend_amount<F, G>(
56    //     config: &Config,
57    //     keychain: &Keychain,
58    //     burnchain: &Burnchain,
59    //     sortdb: &SortitionDB,
60    //     recipients: &[PoxAddress],
61    //     start_mine_height: u64,
62    //     at_burn_block: Option<u64>,
63    //     mut get_prior_winning_prob: F,
64    //     mut set_prior_winning_prob: G,
65    // ) -> u64
66    // where
67    //     F: FnMut(u64) -> f64,
68    //     G: FnMut(u64, f64),
69    // {
70
71    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    /// Checks the `CommandInstance` HCL Block for an attribute named `input.name`
333    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}