roles/
remote_signal.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use hdk::prelude::*;
use roles_integrity::LinkTypes;

use crate::{
    all_role_claims_deleted_proof::create_all_role_claims_deleted_proofs_if_possible,
    linked_devices::get_my_other_devices, role_claim::create_role_claim,
    unassignments::unassign_my_role, utils::create_link_to_link,
};

#[derive(Serialize, Deserialize, Debug)]
pub enum RolesRemoteSignal {
    TryClaimNewRole {
        role: String,
        assignee_to_role_create_link_hash: ActionHash,
    },
    NewPendingUnassignment {
        role: String,
        pending_unassignment_create_link_hash: ActionHash,
    },
    TryCreateAllRoleClaimsDeletedProof {
        deleted_role_claim_hash: ActionHash,
    },
}

#[hdk_extern]
pub fn recv_remote_signal(signal: RolesRemoteSignal) -> ExternResult<()> {
    match signal {
        RolesRemoteSignal::TryClaimNewRole {
            role,
            assignee_to_role_create_link_hash,
        } => {
            info!("A TryClaimNewRole remote signal for the role {role} was just received.");
            retry_until(
                || match get_details(
                    assignee_to_role_create_link_hash.clone(),
                    GetOptions::default(),
                ) {
                    Ok(Some(Details::Record(_))) => true,
                    _ => false,
                },
                10,
            )?;
            let Some(Details::Record(record_details)) = get_details(
                assignee_to_role_create_link_hash.clone(),
                GetOptions::default(),
            )?
            else {
                return Ok(());
            };

            let Action::CreateLink(create_link) = record_details.record.action() else {
                return Err(wasm_error!("Invalid AssigneeToRole link"));
            };

            let Ok(Some(LinkTypes::AssigneeToRole)) =
                LinkTypes::from_type(create_link.zome_index, create_link.link_type)
            else {
                return Err(wasm_error!("Invalid AssigneeToRole link"));
            };

            let Some(target_role_to_assignee_create_link_hash) =
                create_link.target_address.clone().into_action_hash()
            else {
                return Err(wasm_error!(
                    "Invalid AssigneeToRole link: no ActionHash as its target"
                ));
            };

            create_role_claim(
                role,
                assignee_to_role_create_link_hash,
                target_role_to_assignee_create_link_hash,
            )
        }
        RolesRemoteSignal::NewPendingUnassignment {
            pending_unassignment_create_link_hash,
            ..
        } => {
            info!("A NewPendingUnassignment remote signal was just received.");
            retry_until(
                || match get_details(
                    pending_unassignment_create_link_hash.clone(),
                    GetOptions::default(),
                ) {
                    Ok(Some(Details::Record(_))) => true,
                    _ => false,
                },
                10,
            )?;
            debug!("[recv_remote_signal/NewPendingUnassignment] Get details succeeded.");

            let Some(Details::Record(record_details)) = get_details(
                pending_unassignment_create_link_hash.clone(),
                GetOptions::default(),
            )?
            else {
                return Err(wasm_error!("Could not find PendingUnassignment link."));
            };

            let Action::CreateLink(pending_unassignment_create_link) =
                record_details.record.action()
            else {
                return Err(wasm_error!(
                    "Invalid PendingUnassignment record: not a CreateLink."
                ));
            };
            return unassign_my_role(create_link_to_link(
                pending_unassignment_create_link_hash,
                pending_unassignment_create_link.clone(),
            ));
        }
        RolesRemoteSignal::TryCreateAllRoleClaimsDeletedProof {
            deleted_role_claim_hash,
        } => {
            let my_other_devices = get_my_other_devices()?;
            let provenance = call_info()?.provenance;

            if !my_other_devices.contains(&provenance) {
                return Err(wasm_error!("A TryCreateAllRoleClaimsDeletedProof remote signal was received from an agent that is not in our linked devices list."));
            }
            info!("Received a TryCreateAllRoleClaimsDeletedProof remote signal from one of our devices.");
            retry_until(
                || {
                    let Ok(activity) = get_agent_activity(
                        provenance.clone(),
                        ChainQueryFilter::new().action_type(ActionType::DeleteLink),
                        ActivityRequest::Full,
                    ) else {
                        return false;
                    };

                    activity
                        .valid_activity
                        .iter()
                        .find(|(_seq_num, action_hash)| action_hash.eq(&deleted_role_claim_hash))
                        .is_some()
                },
                10,
            )?;
            info!("Found the activity for the new AssignRoleClaim DeleteLink: attempting to create an AllRoleClaimsDeletedProof.");

            create_all_role_claims_deleted_proofs_if_possible(())?;
            Ok(())
        }
    }
}

fn retry_until<F>(task: F, max_retries: u64) -> ExternResult<()>
where
    F: Fn() -> bool,
{
    let mut retry_count = 0;
    while retry_count < max_retries {
        let result = task();
        if result {
            return Ok(());
        }
        retry_count += 1;
        sleep(1_000)?;
    }
    Ok(())
}

fn sleep(ms: u64) -> ExternResult<()> {
    let start = sys_time()?;
    while sys_time()?.as_millis() - start.as_millis() < ms as i64 {}
    Ok(())
}