Skip to content

Commit acbe45a

Browse files
refactor: inject purpose keys structurally through SeismicNode
Instead of relying solely on a global OnceLock side-channel, purpose keys are now stored in SeismicNode and threaded through the node builder lifecycle to SeismicExecutorBuilder. - SeismicNode::new(keys) leaks keys to 'static and stores them - SeismicExecutorBuilder receives keys from SeismicNode::components() - main.rs passes keys via SeismicNode::new() instead of ::default() - Global OnceLock retained as fallback for stage CLI and tests that use SeismicNode::default() Addresses review comment from samlaf on PR #352.
1 parent 9efef55 commit acbe45a

3 files changed

Lines changed: 69 additions & 11 deletions

File tree

bin/seismic-reth/src/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ fn main() {
1818
// Boot enclave and fetch purpose keys BEFORE building node components
1919
let purpose_keys = boot_enclave_and_fetch_keys(&encl).await;
2020

21-
// Store purpose keys in global static storage before building the node
21+
// Store purpose keys in global static storage as a fallback for code
22+
// paths that cannot yet receive keys structurally (e.g. CLI stage command).
2223
reth_seismic_node::purpose_keys::init_purpose_keys(purpose_keys.clone());
2324

2425
// building additional endpoints seismic api
2526
let seismic_api = SeismicApi::new(purpose_keys.clone());
2627

28+
// Inject purpose keys structurally into SeismicNode so they flow
29+
// through the node builder lifecycle without relying on the global.
2730
let node = builder
28-
.node(SeismicNode::default())
31+
.node(SeismicNode::new(purpose_keys.clone()))
2932
.extend_rpc_modules(move |ctx| {
3033
// replace eth_ namespace
3134
ctx.modules.replace_configured(

crates/seismic/node/src/node.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,38 @@ use crate::seismic_evm_config;
5757
/// Storage implementation for Seismic.
5858
pub type SeismicStorage = EthStorage<SeismicTransactionSigned>;
5959

60-
#[derive(Debug, Default, Clone)]
61-
#[non_exhaustive]
60+
#[derive(Debug, Clone)]
6261
/// Type configuration for a regular Seismic node.
63-
pub struct SeismicNode;
62+
///
63+
/// Purpose keys can be injected via [`SeismicNode::new`] so they flow through
64+
/// the node builder lifecycle instead of being read from a global side-channel.
65+
/// When constructed via [`Default`] (e.g. in tests), the executor builder will
66+
/// fall back to the global [`crate::purpose_keys::get_purpose_keys`].
67+
pub struct SeismicNode {
68+
/// Structurally-injected purpose keys. `None` means "use global fallback".
69+
purpose_keys: Option<&'static seismic_enclave::GetPurposeKeysResponse>,
70+
}
71+
72+
impl Default for SeismicNode {
73+
fn default() -> Self {
74+
Self { purpose_keys: None }
75+
}
76+
}
6477

6578
impl SeismicNode {
79+
/// Create a new [`SeismicNode`] with structurally-injected purpose keys.
80+
///
81+
/// The keys are leaked onto the heap so they live for `'static`, which is
82+
/// required by the EVM configuration layer.
83+
pub fn new(purpose_keys: seismic_enclave::GetPurposeKeysResponse) -> Self {
84+
Self { purpose_keys: Some(crate::purpose_keys::leak_purpose_keys(purpose_keys)) }
85+
}
86+
87+
/// Returns the injected purpose keys, if any.
88+
pub fn purpose_keys(&self) -> Option<&'static seismic_enclave::GetPurposeKeysResponse> {
89+
self.purpose_keys
90+
}
91+
6692
/// Returns the components for the given [`EnclaveArgs`].
6793
pub fn components<Node>(
6894
&self,
@@ -83,13 +109,14 @@ impl SeismicNode {
83109
>,
84110
>,
85111
{
112+
let executor = SeismicExecutorBuilder { purpose_keys: self.purpose_keys };
86113
ComponentsBuilder::default()
87114
.node_types::<Node>()
88115
.pool(SeismicPoolBuilder::default())
89-
.executor(SeismicExecutorBuilder::default())
116+
.executor(executor.clone())
90117
.payload(BasicPayloadServiceBuilder::<SeismicPayloadBuilder>::default())
91118
.network(SeismicNetworkBuilder::default())
92-
.executor(SeismicExecutorBuilder::default())
119+
.executor(executor)
93120
.consensus(SeismicConsensusBuilder::default())
94121
}
95122

@@ -404,9 +431,14 @@ where
404431
}
405432

406433
/// A regular seismic evm and executor builder.
407-
#[derive(Debug, Default, Clone, Copy)]
408-
#[non_exhaustive]
409-
pub struct SeismicExecutorBuilder;
434+
///
435+
/// When `purpose_keys` is `Some`, uses the injected keys directly.
436+
/// When `None`, falls back to the global [`crate::purpose_keys::get_purpose_keys`].
437+
#[derive(Debug, Default, Clone)]
438+
pub struct SeismicExecutorBuilder {
439+
/// Structurally-injected purpose keys, or `None` for global fallback.
440+
purpose_keys: Option<&'static seismic_enclave::GetPurposeKeysResponse>,
441+
}
410442

411443
impl<Node> ExecutorBuilder<Node> for SeismicExecutorBuilder
412444
where
@@ -415,7 +447,8 @@ where
415447
type EVM = SeismicEvmConfig;
416448

417449
async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
418-
let purpose_keys = crate::purpose_keys::get_purpose_keys();
450+
let purpose_keys =
451+
self.purpose_keys.unwrap_or_else(|| crate::purpose_keys::get_purpose_keys());
419452
let evm_config = seismic_evm_config(ctx.chain_spec(), purpose_keys);
420453

421454
Ok(evm_config)

crates/seismic/node/src/purpose_keys.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
//!
33
//! This module provides thread-safe access to purpose keys that are fetched once
44
//! during node startup and then used throughout the application lifetime.
5+
//!
6+
//! ## Preferred path: structural injection
7+
//!
8+
//! Purpose keys should be injected into [`SeismicNode::new`](crate::node::SeismicNode::new),
9+
//! which stores them and threads them through the node builder lifecycle
10+
//! (executor builder, etc.). This avoids reliance on global state.
11+
//!
12+
//! ## Deprecated fallback: global `OnceLock`
13+
//!
14+
//! The global [`init_purpose_keys`] / [`get_purpose_keys`] functions are retained
15+
//! as a fallback for code paths that cannot yet receive keys structurally (e.g.
16+
//! the CLI `stage` command, or tests that construct `SeismicNode::default()`).
17+
//! New code should prefer the injection path.
518
619
use seismic_enclave::GetPurposeKeysResponse;
720
use std::sync::OnceLock;
@@ -28,3 +41,12 @@ pub fn init_purpose_keys(keys: GetPurposeKeysResponse) {
2841
pub fn get_purpose_keys() -> &'static GetPurposeKeysResponse {
2942
PURPOSE_KEYS.get().expect("Purpose keys not initialized")
3043
}
44+
45+
/// Leak-box the given keys onto the heap and return a `&'static` reference.
46+
///
47+
/// This is used by [`SeismicNode::new`](crate::node::SeismicNode::new) so that
48+
/// the purpose keys have a `'static` lifetime without relying on the global
49+
/// `OnceLock`.
50+
pub fn leak_purpose_keys(keys: GetPurposeKeysResponse) -> &'static GetPurposeKeysResponse {
51+
Box::leak(Box::new(keys))
52+
}

0 commit comments

Comments
 (0)