iota_node/
handle.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! IotaNodeHandle wraps IotaNode in a way suitable for access by test code.
6//!
7//! When starting a IotaNode directly, in a test (as opposed to using Swarm),
8//! the node may be running inside of a simulator node. It is therefore a
9//! mistake to do something like:
10//!
11//! ```ignore
12//!     use test_utils::authority::{start_node, spawn_checkpoint_processes};
13//!
14//!     let node = start_node(config, registry).await;
15//!     spawn_checkpoint_processes(config, &[node]).await;
16//! ```
17//!
18//! Because this would cause the checkpointing processes to be running inside
19//! the current simulator node rather than the node in which the IotaNode is
20//! running.
21//!
22//! IotaNodeHandle provides an easy way to do the right thing here:
23//!
24//! ```ignore
25//!     let node_handle = start_node(config, registry).await;
26//!     node_handle.with_async(|iota_node| async move {
27//!         spawn_checkpoint_processes(config, &[iota_node]).await;
28//!     });
29//! ```
30//!
31//! Code executed inside of with or with_async will run in the context of the
32//! simulator node. This allows tests to break the simulator abstraction and
33//! magically mutate or inspect state that is conceptually running on a
34//! different "machine", but without producing extremely confusing behavior that
35//! might result otherwise. (For instance, any network connection that is
36//! initiated from a task spawned from within a with or with_async will appear
37//! to originate from the correct simulator node.
38//!
39//! It is possible to exfiltrate state:
40//!
41//! ```ignore
42//!    let state = node_handle.with(|iota_node| iota_node.state);
43//!    // DO NOT DO THIS!
44//!    do_stuff_with_state(state)
45//! ```
46//!
47//! We can't prevent this completely, but we can at least make the right way the
48//! easy way.
49
50use std::{future::Future, sync::Arc};
51
52use iota_core::authority::AuthorityState;
53
54use super::IotaNode;
55
56/// Wrap IotaNode to allow correct access to IotaNode in simulator tests.
57pub struct IotaNodeHandle {
58    node: Option<Arc<IotaNode>>,
59    shutdown_on_drop: bool,
60}
61
62impl IotaNodeHandle {
63    pub fn new(node: Arc<IotaNode>) -> Self {
64        Self {
65            node: Some(node),
66            shutdown_on_drop: false,
67        }
68    }
69
70    pub fn inner(&self) -> &Arc<IotaNode> {
71        self.node.as_ref().unwrap()
72    }
73
74    pub fn with<T>(&self, cb: impl FnOnce(&IotaNode) -> T) -> T {
75        let _guard = self.guard();
76        cb(self.inner())
77    }
78
79    pub fn state(&self) -> Arc<AuthorityState> {
80        self.with(|iota_node| iota_node.state())
81    }
82
83    pub fn shutdown_on_drop(&mut self) {
84        self.shutdown_on_drop = true;
85    }
86}
87
88impl Clone for IotaNodeHandle {
89    fn clone(&self) -> Self {
90        Self {
91            node: self.node.clone(),
92            shutdown_on_drop: false,
93        }
94    }
95}
96
97#[cfg(not(msim))]
98impl IotaNodeHandle {
99    // Must return something to silence lints above at `let _guard = ...`
100    fn guard(&self) -> u32 {
101        0
102    }
103
104    pub async fn with_async<'a, F, R, T>(&'a self, cb: F) -> T
105    where
106        F: FnOnce(&'a IotaNode) -> R,
107        R: Future<Output = T>,
108    {
109        cb(self.inner()).await
110    }
111}
112
113#[cfg(msim)]
114impl IotaNodeHandle {
115    fn guard(&self) -> iota_simulator::runtime::NodeEnterGuard {
116        self.inner().sim_state.sim_node.enter_node()
117    }
118
119    pub async fn with_async<'a, F, R, T>(&'a self, cb: F) -> T
120    where
121        F: FnOnce(&'a IotaNode) -> R,
122        R: Future<Output = T>,
123    {
124        let fut = cb(self.node.as_ref().unwrap());
125        self.inner()
126            .sim_state
127            .sim_node
128            .await_future_in_node(fut)
129            .await
130    }
131}
132
133#[cfg(msim)]
134impl Drop for IotaNodeHandle {
135    fn drop(&mut self) {
136        if self.shutdown_on_drop {
137            let node_id = self.inner().sim_state.sim_node.id();
138            if let Some(h) = iota_simulator::runtime::Handle::try_current() {
139                h.delete_node(node_id);
140            }
141        }
142    }
143}
144
145impl From<Arc<IotaNode>> for IotaNodeHandle {
146    fn from(node: Arc<IotaNode>) -> Self {
147        IotaNodeHandle::new(node)
148    }
149}