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