oasis_runtime_sdk/
enclave_rpc.rs

1//! Exposed EnclaveRPC methods.
2use std::{marker::PhantomData, sync::Arc};
3
4use anyhow::{anyhow, bail, Result};
5
6use crate::{
7    context::RuntimeBatchContext,
8    core::{
9        consensus::{
10            roothash::Header,
11            state::{
12                beacon::ImmutableState as BeaconState, registry::ImmutableState as RegistryState,
13                roothash::ImmutableState as RoothashState,
14            },
15            verifier::Verifier,
16        },
17        enclave_rpc::{
18            dispatcher::{
19                Dispatcher as RpcDispatcher, Method as RpcMethod,
20                MethodDescriptor as RpcMethodDescriptor,
21            },
22            types::Kind as RpcKind,
23            Context as RpcContext,
24        },
25        future::block_on,
26        protocol::{HostInfo, Protocol},
27        storage::mkvs,
28    },
29    dispatcher,
30    keymanager::KeyManagerClient,
31    module::MethodHandler,
32    state::{self, CurrentState},
33    storage::HostStore,
34    Runtime,
35};
36
37/// Name of the `query` method.
38pub const METHOD_QUERY: &str = "runtime-sdk/query";
39
40/// Arguments for the `query` method.
41#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
42pub struct QueryRequest {
43    pub round: u64,
44    pub method: String,
45    pub args: Vec<u8>,
46}
47
48/// EnclaveRPC dispatcher wrapper.
49pub(crate) struct Wrapper<R: Runtime> {
50    host_info: HostInfo,
51    host: Arc<Protocol>,
52    key_manager: Option<Arc<KeyManagerClient>>,
53    consensus_verifier: Arc<dyn Verifier>,
54    _runtime: PhantomData<R>,
55}
56
57impl<R> Wrapper<R>
58where
59    R: Runtime + Send + Sync + 'static,
60{
61    pub(crate) fn wrap(
62        rpc: &mut RpcDispatcher,
63        host: Arc<Protocol>,
64        host_info: HostInfo,
65        key_manager: Option<Arc<KeyManagerClient>>,
66        consensus_verifier: Arc<dyn Verifier>,
67    ) {
68        let wrapper = Box::leak(Box::new(Self {
69            host_info,
70            host,
71            key_manager,
72            consensus_verifier,
73            _runtime: PhantomData,
74        }));
75        rpc.add_methods(wrapper.methods());
76    }
77
78    fn methods(&'static self) -> Vec<RpcMethod> {
79        vec![RpcMethod::new(
80            RpcMethodDescriptor {
81                name: METHOD_QUERY.to_string(),
82                kind: RpcKind::NoiseSession,
83            },
84            move |ctx: &_, req: &_| self.rpc_query(ctx, req),
85        )]
86    }
87
88    fn ensure_session_endorsed(&self, ctx: &RpcContext) -> Result<()> {
89        let endorsed_by = ctx
90            .session_info
91            .as_ref()
92            .ok_or(anyhow!("not authorized"))?
93            .endorsed_by
94            .ok_or(anyhow!("not endorsed by host"))?;
95        let host_identity = self
96            .host
97            .get_identity()
98            .ok_or(anyhow!("local identity not available"))?
99            .node_identity()
100            .ok_or(anyhow!("node identity not available"))?;
101        if endorsed_by != host_identity {
102            bail!("not endorsed by host");
103        }
104        Ok(())
105    }
106
107    fn rpc_query(&self, ctx: &RpcContext, req: &QueryRequest) -> Result<Vec<u8>> {
108        self.ensure_session_endorsed(ctx)?;
109
110        // Determine whether the method is allowed to access confidential state and provide an
111        // appropriately scoped instance of the key manager client.
112        let is_confidential_allowed = R::Modules::is_allowed_private_km_query(&req.method)
113            && R::is_allowed_private_km_query(&req.method);
114        let key_manager = self.key_manager.as_ref().map(|mgr| {
115            if is_confidential_allowed {
116                mgr.with_private_context()
117            } else {
118                mgr.with_context()
119            }
120        });
121
122        // Fetch latest consensus layer state.
123        let state = block_on(self.consensus_verifier.latest_state())?;
124        let roothash = RoothashState::new(&state);
125        let roots = roothash
126            .round_roots(self.host_info.runtime_id, req.round)?
127            .ok_or(anyhow!("root not found"))?;
128        let beacon = BeaconState::new(&state);
129        let epoch = beacon.epoch()?;
130        let registry = RegistryState::new(&state);
131        let runtime = registry
132            .runtime(&self.host_info.runtime_id)?
133            .ok_or(anyhow!("runtime not found"))?;
134
135        // Prepare dispatch context.
136        let history = self.consensus_verifier.clone();
137        let root = HostStore::new(
138            self.host.clone(),
139            mkvs::Root {
140                namespace: self.host_info.runtime_id,
141                version: req.round,
142                root_type: mkvs::RootType::State,
143                hash: roots.state_root,
144            },
145        );
146        // TODO: This is currently limited as we have no nice way of getting a good known header. We
147        // need to expose more stuff in roothash and then limit the query to latest round. Until
148        // then any queries requiring access to features like timestamp will fail as we need to
149        // ensure we use safe values for these arguments.
150        let header = Header {
151            namespace: self.host_info.runtime_id,
152            round: req.round,
153            io_root: roots.io_root,
154            state_root: roots.state_root,
155            ..Default::default()
156        };
157        let round_results = Default::default();
158        let max_messages = runtime.executor.max_messages;
159
160        let ctx = RuntimeBatchContext::<'_, R>::new(
161            &self.host_info,
162            key_manager,
163            &header,
164            &round_results,
165            &state,
166            &history,
167            epoch,
168            max_messages,
169        );
170
171        CurrentState::enter_opts(
172            state::Options::new()
173                .with_mode(state::Mode::Check)
174                .with_rng_local_entropy(), // Mix in local (private) entropy for queries.
175            root,
176            || dispatcher::Dispatcher::<R>::dispatch_query(&ctx, &req.method, req.args.clone()),
177        )
178        .map_err(Into::into)
179    }
180}