oasis_runtime_sdk/
runtime.rs

1//! Runtime.
2use std::sync::Arc;
3
4use oasis_core_runtime::{
5    common::{sgx, version},
6    config::Config,
7    consensus::verifier::TrustRoot,
8    dispatcher::{PostInitState, PreInitState},
9    enclave_rpc::session,
10    start_runtime,
11    types::{FeatureScheduleControl, Features},
12};
13
14use crate::{
15    config,
16    context::Context,
17    crypto, dispatcher,
18    keymanager::{KeyManagerClient, TrustedSigners},
19    module::{
20        BlockHandler, FeeProxyHandler, InvariantHandler, MethodHandler, MigrationHandler,
21        ModuleInfoHandler, TransactionHandler,
22    },
23    modules,
24    state::CurrentState,
25    storage::{self},
26};
27
28/// A runtime.
29pub trait Runtime {
30    /// Runtime version.
31    const VERSION: version::Version;
32    /// State version.
33    const STATE_VERSION: u32 = 0;
34
35    /// Prefetch limit. To enable prefetch set it to a non-zero value.
36    const PREFETCH_LIMIT: u16 = 0;
37
38    /// Runtime schedule control configuration.
39    const SCHEDULE_CONTROL: config::ScheduleControl = config::ScheduleControl::default();
40
41    /// Maximum delta that the transaction nonce can be in the future from the current nonce
42    /// to still be accepted during transaction checks.
43    const MAX_CHECK_NONCE_FUTURE_DELTA: u64 = 0;
44
45    /// Module that provides the core API.
46    type Core: modules::core::API;
47    /// Module that provides the accounts API.
48    type Accounts: modules::accounts::API;
49    /// Handler for proxy fee payments.
50    type FeeProxy: FeeProxyHandler = ();
51
52    /// Supported modules.
53    type Modules: TransactionHandler
54        + MigrationHandler
55        + MethodHandler
56        + BlockHandler
57        + InvariantHandler
58        + ModuleInfoHandler;
59
60    /// Return the trusted signers for this runtime; if `None`, a key manager connection will not be
61    /// established on startup.
62    fn trusted_signers() -> Option<TrustedSigners> {
63        None
64    }
65
66    /// Return the consensus layer trust root for this runtime; if `None`, consensus layer integrity
67    /// verification will not be performed.
68    fn consensus_trust_root() -> Option<TrustRoot> {
69        None
70    }
71
72    /// Genesis state for the runtime.
73    fn genesis_state() -> <Self::Modules as MigrationHandler>::Genesis;
74
75    /// Perform runtime-specific state migration. This method is only called when the recorded
76    /// state version does not match `STATE_VERSION`.
77    fn migrate_state<C: Context>(_ctx: &C) {
78        // Default implementation doesn't perform any migration.
79    }
80
81    /// Whether a given query method is allowed to be invoked.
82    fn is_allowed_query(_method: &str) -> bool {
83        true
84    }
85
86    /// Whether a given query method is allowed to access private key manager state.
87    ///
88    /// Note that even if this returns `true` for a method, the method also needs to be tagged as
89    /// being allowed to access private key manager state (e.g. with `allow_private_km`).
90    fn is_allowed_private_km_query(_method: &str) -> bool {
91        true
92    }
93
94    /// Whether a given call is allowed to be invoked interactively.
95    ///
96    /// Note that even if this returns `true` for a method, the method also needs to be tagged as
97    /// being allowed to be executed interactively (e.g. with `allow_interactive`)
98    fn is_allowed_interactive_call(_method: &str) -> bool {
99        true
100    }
101
102    /// Perform state migrations if required.
103    fn migrate<C: Context>(ctx: &C) {
104        let mut metadata = CurrentState::with_store(|store| {
105            let store = storage::TypedStore::new(storage::PrefixStore::new(
106                store,
107                &modules::core::MODULE_NAME,
108            ));
109            let metadata: modules::core::types::Metadata = store
110                .get(modules::core::state::METADATA)
111                .unwrap_or_default();
112
113            metadata
114        });
115
116        // Perform state migrations/initialization on all modules.
117        let mut has_changes =
118            Self::Modules::init_or_migrate(ctx, &mut metadata, Self::genesis_state());
119
120        // Check if we need to also apply any global state updates.
121        let global_version = metadata
122            .versions
123            .get(modules::core::types::VERSION_GLOBAL_KEY)
124            .copied()
125            .unwrap_or_default();
126        if global_version != Self::STATE_VERSION
127            && !CurrentState::with_env(|env| env.is_check_only())
128        {
129            assert!(
130                // There should either be no state, or it should be the previous version.
131                global_version == 0 || global_version == Self::STATE_VERSION - 1,
132                "inconsistent existing state version (expected: {} got: {})",
133                Self::STATE_VERSION - 1,
134                global_version
135            );
136
137            Self::migrate_state(ctx);
138
139            // Update metadata.
140            metadata.versions.insert(
141                modules::core::types::VERSION_GLOBAL_KEY.to_string(),
142                Self::STATE_VERSION,
143            );
144            has_changes = true;
145        }
146
147        // If there are any changes, update metadata.
148        if has_changes {
149            CurrentState::with_store(|store| {
150                let mut store = storage::TypedStore::new(storage::PrefixStore::new(
151                    store,
152                    &modules::core::MODULE_NAME,
153                ));
154                store.insert(modules::core::state::METADATA, metadata);
155            });
156        }
157    }
158
159    /// Start the runtime.
160    fn start()
161    where
162        Self: Sized + Send + Sync + 'static,
163    {
164        // Initializer.
165        let init = |state: PreInitState<'_>| -> PostInitState {
166            // Fetch host information and configure domain separation context.
167            let hi = state.protocol.get_host_info();
168            crypto::signature::context::set_chain_context(
169                hi.runtime_id,
170                &hi.consensus_chain_context,
171            );
172
173            // Cobble together a keymanager client.
174            let key_manager = Self::trusted_signers().map(|signers| {
175                Arc::new(KeyManagerClient::new(
176                    hi.runtime_id,
177                    state.protocol.clone(),
178                    state.consensus_verifier.clone(),
179                    state.identity.clone(),
180                    state.rpc_dispatcher,
181                    4096,
182                    signers,
183                ))
184            });
185
186            // Create the transaction dispatcher.
187            let dispatcher = dispatcher::Dispatcher::<Self>::new(
188                state.protocol.clone(),
189                key_manager,
190                state.consensus_verifier.clone(),
191            );
192            // Register EnclaveRPC methods.
193            dispatcher.register_enclaverpc(state.rpc_dispatcher);
194            // Make sure we allow a wide amount of valid quotes for EnclaveRPC because our clients
195            // are other runtime components and we should accept them all.
196            let session_builder = session::Builder::default()
197                .local_identity(state.identity.clone())
198                .quote_policy(Some(Arc::new(sgx::QuotePolicy {
199                    ias: Some(sgx::ias::QuotePolicy {
200                        disabled: true, // Disable legacy EPID attestation.
201                        ..Default::default()
202                    }),
203                    pcs: Some(sgx::pcs::QuotePolicy {
204                        // Allow TDX since that is not part of the default policy.
205                        tdx: Some(sgx::pcs::TdxQuotePolicy {
206                            allowed_tdx_modules: vec![],
207                        }),
208                        ..Default::default()
209                    }),
210                })));
211            state.rpc_demux.set_session_builder(session_builder);
212
213            PostInitState {
214                txn_dispatcher: Some(Box::new(dispatcher)),
215                app: None,
216            }
217        };
218
219        // Configure the runtime features.
220        let features = Features {
221            schedule_control: Some(FeatureScheduleControl {
222                initial_batch_size: Self::SCHEDULE_CONTROL.initial_batch_size,
223            }),
224            ..Default::default()
225        };
226
227        // Start the runtime.
228        start_runtime(
229            Box::new(init),
230            Config {
231                version: Self::VERSION,
232                trust_root: Self::consensus_trust_root(),
233                features,
234                persist_check_tx_state: false,
235                ..Default::default()
236            },
237        );
238    }
239}