holochain_types/
countersigning.rs

1//! Types related to countersigning sessions.
2
3use holo_hash::{AgentPubKey, EntryHash};
4use holochain_timestamp::Timestamp;
5use holochain_zome_types::{
6    cell::CellId,
7    prelude::PreflightRequest,
8    record::{SignedAction, SignedActionHashed},
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13/// State and data of an ongoing countersigning session.
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum CountersigningSessionState {
17    /// This is the entry state. Accepting a countersigning session through the HDK will immediately
18    /// register the countersigning session in this state, for management by the countersigning workflow.
19    ///
20    /// The session will stay in this state even when the agent commits their countersigning entry and only
21    /// move to the next state when the first signature bundle is received.
22    Accepted(PreflightRequest),
23    /// This is the state where we have collected one or more signatures for a countersigning session.
24    ///
25    /// This state can be entered from the [CountersigningSessionState::Accepted] state, which happens
26    /// when a witness returns a signature bundle to us. While the session has not timed out, we will
27    /// stay in this state and wait until one of the signatures bundles we have received is valid for
28    /// the session to be completed.
29    ///
30    /// If we entered this state from the [CountersigningSessionState::Accepted] state, we will either
31    /// complete the session successfully or the session will time out. On a timeout we will move
32    /// to the [CountersigningSessionState::Unknown] for a limited number of attempts to recover the session.
33    ///
34    /// This state can also be entered from the [CountersigningSessionState::Unknown] state, which happens when we
35    /// have been able to recover the session from the source chain and have requested signed actions
36    /// from agent authorities to build a signature bundle.
37    ///
38    /// If we entered this state from the [CountersigningSessionState::Unknown] state, we will either
39    /// complete the session successfully, or if the signatures are invalid, we will return to the
40    /// [CountersigningSessionState::Unknown] state.
41    SignaturesCollected {
42        /// The preflight request that has been exchanged among countersigning peers.
43        preflight_request: PreflightRequest,
44        /// Signed actions of the committed countersigned entries of all participating peers.
45        signature_bundles: Vec<Vec<SignedAction>>,
46        /// This field is set when the signature bundle came from querying agent activity authorities
47        /// in the unknown state. If we started from that state, we should return to it if the
48        /// signature bundle is invalid. Otherwise, stay in this state and wait for more signatures.
49        resolution: Option<SessionResolutionSummary>,
50    },
51    /// The session is in an unknown state and needs to be resolved.
52    ///
53    /// This state is used when we have lost track of the countersigning session. This happens if
54    /// we have got far enough to create the countersigning entry but have crashed or restarted
55    /// before we could complete the session. In this case we need to try to discover what the other
56    /// agent or agents involved in the session have done.
57    ///
58    /// This state is also entered temporarily when we have published a signature and then the
59    /// session has timed out. To avoid deadlocking with two parties both waiting for each other to
60    /// proceed, we cannot stay in this state indefinitely. We will make a limited number of attempts
61    /// to recover and if we cannot, we will abandon the session.
62    ///
63    /// The only exception to the attempt limiting is if we are unable to reach agent activity authorities
64    /// to progress resolving the session. In this case, the attempts are not counted towards the
65    /// configured limit. This does not protect us against a network partition where we can only see
66    /// a subset of the network, but it does protect us against Holochain forcing a decision while
67    /// it is unable to reach any peers.
68    ///
69    /// Note that because the [PreflightRequest] is stored here, we only ever enter the unknown state
70    /// if we managed to keep the preflight request in memory, or if we have been able to recover it
71    /// from the source chain as part of the committed countersigning session data. Otherwise, we
72    /// are unable to discover what session we were participating in, and we must abandon the session
73    /// without going through this recovery state.
74    Unknown {
75        /// The preflight request that has been exchanged.
76        preflight_request: PreflightRequest,
77        /// Summary of the attempts to resolve this session.
78        resolution: SessionResolutionSummary,
79        /// Flag if the session is programmed to be force-abandoned on the next countersigning workflow run.
80        force_abandon: bool,
81        /// Flag if the session is programmed to be force-published on the next countersigning workflow run.
82        force_publish: bool,
83    },
84}
85
86impl CountersigningSessionState {
87    /// Get preflight request of the countersigning session.
88    pub fn preflight_request(&self) -> &PreflightRequest {
89        match self {
90            CountersigningSessionState::Accepted(preflight_request) => preflight_request,
91            CountersigningSessionState::SignaturesCollected {
92                preflight_request, ..
93            } => preflight_request,
94            CountersigningSessionState::Unknown {
95                preflight_request, ..
96            } => preflight_request,
97        }
98    }
99
100    /// Get app entry hash from preflight request.
101    pub fn session_app_entry_hash(&self) -> &EntryHash {
102        let request = match self {
103            CountersigningSessionState::Accepted(request) => request,
104            CountersigningSessionState::SignaturesCollected {
105                preflight_request, ..
106            } => preflight_request,
107            CountersigningSessionState::Unknown {
108                preflight_request, ..
109            } => preflight_request,
110        };
111
112        &request.app_entry_hash
113    }
114}
115
116/// Summary of the workflow's attempts to resolve the outcome a failed countersigning session.
117///
118/// This tracks the numbers of attempts and the outcome of the most recent attempt.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct SessionResolutionSummary {
121    /// The reason why session resolution is required.
122    pub required_reason: ResolutionRequiredReason,
123    /// How many attempts have been made to resolve the session.
124    ///
125    /// This count is only correct for the current run of the Holochain conductor. If the conductor
126    /// is restarted then this counter is also reset.
127    pub attempts: usize,
128    /// The time of the last attempt to resolve the session.
129    pub last_attempt_at: Option<Timestamp>,
130    /// The outcome of the most recent attempt to resolve the session.
131    pub outcomes: Vec<SessionResolutionOutcome>,
132}
133
134impl Default for SessionResolutionSummary {
135    fn default() -> Self {
136        Self {
137            required_reason: ResolutionRequiredReason::Unknown,
138            attempts: 0,
139            last_attempt_at: None,
140            outcomes: Vec::with_capacity(0),
141        }
142    }
143}
144
145/// The reason why a countersigning session can not be resolved automatically and requires manual resolution.
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub enum ResolutionRequiredReason {
148    /// The session has timed out, so we should try to resolve its state before abandoning.
149    Timeout,
150    /// Something happened, like a conductor restart, and we lost track of the session.
151    Unknown,
152}
153
154/// The outcome for a single agent who participated in a countersigning session.
155///
156/// [NUM_AUTHORITIES_TO_QUERY] authorities are made to agent activity authorities for each agent,
157/// and the decisions are collected into [SessionResolutionOutcome::decisions].
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct SessionResolutionOutcome {
160    /// The agent who participated in the countersigning session and is the subject of this
161    /// resolution outcome.
162    // Unused until the next PR
163    #[allow(dead_code)]
164    pub agent: AgentPubKey,
165    /// The resolved decision for each authority for the subject agent.
166    // Unused until the next PR
167    #[allow(dead_code)]
168    pub decisions: Vec<SessionCompletionDecision>,
169}
170
171/// Number of authorities to be queried for agent activity, in an attempt to resolve a countersigning
172/// session in an unknown state.
173pub const NUM_AUTHORITIES_TO_QUERY: usize = 3;
174
175/// Decision about an incomplete countersigning session.
176#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
177pub enum SessionCompletionDecision {
178    /// Evidence found on the network that this session completed successfully.
179    Complete(Box<SignedActionHashed>),
180    /// Evidence found on the network that this session was abandoned and other agents have
181    /// added to their chain without completing the session.
182    Abandoned,
183    /// No evidence, or inconclusive evidence, was found on the network. Holochain will not make an
184    /// automatic decision until the evidence is conclusive.
185    Indeterminate,
186    /// There were errors encountered while trying to resolve the session. Errors such as network
187    /// errors are treated differently to inconclusive evidence. We don't want to force a decision
188    /// when we're offline, for example. In this case, the resolution must be retried later and this
189    /// attempt should not be counted.
190    Failed,
191}
192
193/// Errors related to countersigning sessions.
194#[derive(Debug, Error)]
195pub enum CountersigningError {
196    /// Countersigning workspace does not exist for cell.
197    #[error("Countersigning workspace does not exist for cell id {0:?}. Probably an invalid cell id was provided.")]
198    WorkspaceDoesNotExist(CellId),
199    /// No countersigning session found for the cell.
200    #[error("No countersigning session found for cell id {0:?}")]
201    SessionNotFound(CellId),
202    /// Countersigning session must be in an unresolved state to be abandoned or published.
203    #[error("Countersigning session for cell id {0:?} is not unresolved. Only unresolved sessions can be abandoned or published.")]
204    SessionNotUnresolved(CellId),
205}