oasis_runtime_sdk/modules/rofl/
mod.rs

1//! On-chain coordination for ROFL components.
2use std::collections::{BTreeMap, BTreeSet};
3
4use once_cell::sync::Lazy;
5
6use crate::{
7    context::Context,
8    core::{
9        common::crypto::signature::PublicKey as CorePublicKey,
10        consensus::{
11            registry::{Node, RolesMask, VerifiedEndorsedCapabilityTEE},
12            state::registry::ImmutableState as RegistryImmutableState,
13        },
14    },
15    crypto::signature::PublicKey,
16    dispatcher, handler, keymanager, migration,
17    module::{self, Module as _, Parameters as _},
18    modules::{self, accounts::API as _, core::API as _},
19    sdk_derive,
20    state::CurrentState,
21    types::{address::Address, transaction::Transaction},
22    Runtime,
23};
24
25pub mod app;
26pub mod app_id;
27mod config;
28mod error;
29mod event;
30pub mod policy;
31pub mod state;
32#[cfg(test)]
33mod test;
34pub mod types;
35
36/// Unique module name.
37const MODULE_NAME: &str = "rofl";
38
39pub use config::Config;
40pub use error::Error;
41pub use event::Event;
42
43/// Parameters for the module.
44#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
45pub struct Parameters {}
46
47/// Errors emitted during parameter validation.
48#[derive(thiserror::Error, Debug)]
49pub enum ParameterValidationError {}
50
51impl module::Parameters for Parameters {
52    type Error = ParameterValidationError;
53
54    fn validate_basic(&self) -> Result<(), Self::Error> {
55        Ok(())
56    }
57}
58
59/// Genesis state for the module.
60#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
61pub struct Genesis {
62    pub parameters: Parameters,
63
64    /// Application configurations.
65    pub apps: Vec<types::AppConfig>,
66}
67
68/// Interface that can be called from other modules.
69pub trait API {
70    /// Get the Runtime Attestation Key of the ROFL app instance in case the origin transaction is
71    /// signed by a ROFL instance. Otherwise `None` is returned.
72    ///
73    /// # Panics
74    ///
75    /// This method will panic if called outside a transaction environment.
76    fn get_origin_rak() -> Option<PublicKey>;
77
78    /// Get the registration descriptor of the ROFL app instance in case the origin transaction is
79    /// signed by a ROFL instance of the specified app. Otherwise `None` is returned.
80    ///
81    /// # Panics
82    ///
83    /// This method will panic if called outside a transaction environment.
84    fn get_origin_registration(app: app_id::AppId) -> Option<types::Registration>;
85
86    /// Verify whether the origin transaction is signed by an authorized ROFL instance for the given
87    /// application.
88    ///
89    /// # Panics
90    ///
91    /// This method will panic if called outside a transaction environment.
92    fn is_authorized_origin(app: app_id::AppId) -> bool;
93
94    /// Get a specific registered instance for an application.
95    fn get_registration(app: app_id::AppId, rak: PublicKey) -> Result<types::Registration, Error>;
96
97    /// Get an application's configuration.
98    fn get_app(id: app_id::AppId) -> Result<types::AppConfig, Error>;
99
100    /// Get all application configurations.
101    fn get_apps() -> Result<Vec<types::AppConfig>, Error>;
102
103    /// Get all registered instances for an application.
104    fn get_instances(id: app_id::AppId) -> Result<Vec<types::Registration>, Error>;
105}
106
107/// Module's address that has the application stake pool.
108///
109/// oasis1qza6sddnalgzexk3ct30gqfvntgth5m4hsyywmff
110pub static ADDRESS_APP_STAKE_POOL: Lazy<Address> =
111    Lazy::new(|| Address::from_module(MODULE_NAME, "app-stake-pool"));
112
113/// Key derivation context.
114pub static ROFL_DERIVE_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/rofl: derive key v1";
115/// Secrets encryption key identifier.
116pub static ROFL_KEY_ID_SEK: &[u8] = b"oasis-runtime-sdk/rofl: secrets encryption key v1";
117
118pub struct Module<Cfg: Config> {
119    _cfg: std::marker::PhantomData<Cfg>,
120}
121
122impl<Cfg: Config> API for Module<Cfg> {
123    fn get_origin_rak() -> Option<PublicKey> {
124        let caller_pk = CurrentState::with_env_origin(|env| env.tx_caller_public_key())?;
125
126        // Resolve RAK as the call may be made by an extra key.
127        state::get_endorser(&caller_pk).map(|kei| match kei {
128            // It may point to a RAK.
129            state::KeyEndorsementInfo { rak: Some(rak), .. } => rak.into(),
130            // Or it points to itself.
131            _ => caller_pk,
132        })
133    }
134
135    fn get_origin_registration(app: app_id::AppId) -> Option<types::Registration> {
136        Self::get_origin_rak()
137            .and_then(|rak| state::get_registration(app, &rak.try_into().unwrap()))
138    }
139
140    fn is_authorized_origin(app: app_id::AppId) -> bool {
141        Self::get_origin_registration(app).is_some()
142    }
143
144    fn get_registration(app: app_id::AppId, rak: PublicKey) -> Result<types::Registration, Error> {
145        state::get_registration(app, &rak.try_into().map_err(|_| Error::InvalidArgument)?)
146            .ok_or(Error::UnknownInstance)
147    }
148
149    fn get_app(id: app_id::AppId) -> Result<types::AppConfig, Error> {
150        state::get_app(id).ok_or(Error::UnknownApp)
151    }
152
153    fn get_apps() -> Result<Vec<types::AppConfig>, Error> {
154        Ok(state::get_apps())
155    }
156
157    fn get_instances(id: app_id::AppId) -> Result<Vec<types::Registration>, Error> {
158        Ok(state::get_registrations_for_app(id))
159    }
160}
161
162#[sdk_derive(Module)]
163impl<Cfg: Config> Module<Cfg> {
164    const NAME: &'static str = MODULE_NAME;
165    type Error = Error;
166    type Event = Event;
167    type Parameters = Parameters;
168    type Genesis = Genesis;
169
170    #[migration(init)]
171    fn init(genesis: Genesis) {
172        genesis
173            .parameters
174            .validate_basic()
175            .expect("invalid genesis parameters");
176
177        // Set genesis parameters.
178        Self::set_params(genesis.parameters);
179
180        // Insert all applications.
181        for cfg in genesis.apps {
182            if state::get_app(cfg.id).is_some() {
183                panic!("duplicate application in genesis: {:?}", cfg.id);
184            }
185
186            state::set_app(cfg);
187        }
188    }
189
190    /// Create a new ROFL application.
191    #[handler(call = "rofl.Create")]
192    fn tx_create<C: Context>(ctx: &C, body: types::Create) -> Result<app_id::AppId, Error> {
193        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_CREATE)?;
194
195        if body.metadata.len() > Cfg::MAX_METADATA_PAIRS {
196            return Err(Error::InvalidArgument);
197        }
198        for (key, value) in &body.metadata {
199            if key.len() > Cfg::MAX_METADATA_KEY_SIZE {
200                return Err(Error::InvalidArgument);
201            }
202            if value.len() > Cfg::MAX_METADATA_VALUE_SIZE {
203                return Err(Error::InvalidArgument);
204            }
205        }
206
207        if CurrentState::with_env(|env| env.is_check_only()) {
208            return Ok(Default::default());
209        }
210
211        let (creator, tx_index) =
212            CurrentState::with_env(|env| (env.tx_caller_address(), env.tx_index()));
213        let app_id = match body.scheme {
214            types::IdentifierScheme::CreatorRoundIndex => app_id::AppId::from_creator_round_index(
215                creator,
216                ctx.runtime_header().round,
217                tx_index.try_into().map_err(|_| Error::InvalidArgument)?,
218            ),
219            types::IdentifierScheme::CreatorNonce => {
220                let nonce = <C::Runtime as Runtime>::Accounts::get_nonce(creator)?;
221
222                app_id::AppId::from_creator_nonce(creator, nonce)
223            }
224        };
225
226        // Sanity check that the application doesn't already exist.
227        if state::get_app(app_id).is_some() {
228            return Err(Error::AppAlreadyExists);
229        }
230
231        // Transfer stake.
232        <C::Runtime as Runtime>::Accounts::transfer(
233            creator,
234            *ADDRESS_APP_STAKE_POOL,
235            &Cfg::STAKE_APP_CREATE,
236        )?;
237
238        // Generate the secret encryption (public) key.
239        let sek = Self::derive_app_key(
240            ctx,
241            &app_id,
242            types::KeyKind::X25519,
243            types::KeyScope::Global,
244            ROFL_KEY_ID_SEK,
245            None,
246        )?
247        .input_keypair
248        .pk;
249
250        // Register the application.
251        let cfg = types::AppConfig {
252            id: app_id,
253            policy: body.policy,
254            admin: Some(creator),
255            stake: Cfg::STAKE_APP_CREATE,
256            metadata: body.metadata,
257            sek,
258            ..Default::default()
259        };
260        state::set_app(cfg);
261
262        CurrentState::with(|state| state.emit_event(Event::AppCreated { id: app_id }));
263
264        Ok(app_id)
265    }
266
267    /// Ensure caller is the current administrator, return an error otherwise.
268    fn ensure_caller_is_admin(cfg: &types::AppConfig) -> Result<(), Error> {
269        let caller = CurrentState::with_env(|env| env.tx_caller_address());
270        if cfg.admin != Some(caller) {
271            return Err(Error::Forbidden);
272        }
273        Ok(())
274    }
275
276    /// Update a ROFL application.
277    #[handler(call = "rofl.Update")]
278    fn tx_update<C: Context>(ctx: &C, body: types::Update) -> Result<(), Error> {
279        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_UPDATE)?;
280
281        if body.metadata.len() > Cfg::MAX_METADATA_PAIRS {
282            return Err(Error::InvalidArgument);
283        }
284        for (key, value) in &body.metadata {
285            if key.len() > Cfg::MAX_METADATA_KEY_SIZE {
286                return Err(Error::InvalidArgument);
287            }
288            if value.len() > Cfg::MAX_METADATA_VALUE_SIZE {
289                return Err(Error::InvalidArgument);
290            }
291        }
292        if body.secrets.len() > Cfg::MAX_METADATA_PAIRS {
293            return Err(Error::InvalidArgument);
294        }
295        for (key, value) in &body.secrets {
296            if key.len() > Cfg::MAX_METADATA_KEY_SIZE {
297                return Err(Error::InvalidArgument);
298            }
299            if value.len() > Cfg::MAX_METADATA_VALUE_SIZE {
300                return Err(Error::InvalidArgument);
301            }
302        }
303
304        let mut cfg = state::get_app(body.id).ok_or(Error::UnknownApp)?;
305
306        // Ensure caller is the admin and is allowed to update the configuration.
307        Self::ensure_caller_is_admin(&cfg)?;
308
309        if CurrentState::with_env(|env| env.is_check_only()) {
310            return Ok(());
311        }
312
313        // If there is no SEK defined, regenerate it.
314        if cfg.sek == Default::default() {
315            cfg.sek = Self::derive_app_key(
316                ctx,
317                &body.id,
318                types::KeyKind::X25519,
319                types::KeyScope::Global,
320                ROFL_KEY_ID_SEK,
321                None,
322            )?
323            .input_keypair
324            .pk;
325        }
326
327        cfg.policy = body.policy;
328        cfg.admin = body.admin;
329        cfg.metadata = body.metadata;
330        cfg.secrets = body.secrets;
331        state::set_app(cfg);
332
333        CurrentState::with(|state| state.emit_event(Event::AppUpdated { id: body.id }));
334
335        Ok(())
336    }
337
338    /// Remove a ROFL application.
339    #[handler(call = "rofl.Remove")]
340    fn tx_remove<C: Context>(ctx: &C, body: types::Remove) -> Result<(), Error> {
341        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_REMOVE)?;
342
343        let cfg = state::get_app(body.id).ok_or(Error::UnknownApp)?;
344
345        // Ensure caller is the admin and is allowed to update the configuration.
346        Self::ensure_caller_is_admin(&cfg)?;
347
348        if CurrentState::with_env(|env| env.is_check_only()) {
349            return Ok(());
350        }
351
352        state::remove_app(body.id);
353
354        // Return stake to the administrator account.
355        if let Some(admin) = cfg.admin {
356            <C::Runtime as Runtime>::Accounts::transfer(
357                *ADDRESS_APP_STAKE_POOL,
358                admin,
359                &cfg.stake,
360            )?;
361        }
362
363        CurrentState::with(|state| state.emit_event(Event::AppRemoved { id: body.id }));
364
365        Ok(())
366    }
367
368    /// Register a new ROFL instance.
369    #[handler(call = "rofl.Register")]
370    fn tx_register<C: Context>(ctx: &C, body: types::Register) -> Result<(), Error> {
371        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_REGISTER)?;
372
373        if body.expiration <= ctx.epoch() {
374            return Err(Error::RegistrationExpired);
375        }
376
377        if body.metadata.len() > Cfg::MAX_METADATA_PAIRS {
378            return Err(Error::InvalidArgument);
379        }
380        for (key, value) in &body.metadata {
381            if key.len() > Cfg::MAX_METADATA_KEY_SIZE {
382                return Err(Error::InvalidArgument);
383            }
384            if value.len() > Cfg::MAX_METADATA_VALUE_SIZE {
385                return Err(Error::InvalidArgument);
386            }
387        }
388
389        let cfg = state::get_app(body.app).ok_or(Error::UnknownApp)?;
390
391        if body.expiration - ctx.epoch() > cfg.policy.max_expiration {
392            return Err(Error::InvalidArgument);
393        }
394
395        // Ensure that the transaction is signed by RAK (and co-signed by extra keys).
396        let signer_pks: BTreeSet<PublicKey> = CurrentState::with_env(|env| {
397            env.tx_auth_info()
398                .signer_info
399                .iter()
400                .filter_map(|si| si.address_spec.public_key())
401                .collect()
402        });
403        if !signer_pks.contains(&body.ect.capability_tee.rak.into()) {
404            return Err(Error::NotSignedByRAK);
405        }
406        for extra_pk in &body.extra_keys {
407            if !signer_pks.contains(extra_pk) {
408                return Err(Error::NotSignedByExtraKey);
409            }
410        }
411
412        if CurrentState::with_env(|env| env.is_check_only()) {
413            return Ok(());
414        }
415
416        // Verify policy.
417        let verified_ect = body
418            .ect
419            .verify(&cfg.policy.quotes)
420            .map_err(|_| Error::InvalidArgument)?;
421
422        // Verify enclave identity.
423        if !cfg
424            .policy
425            .enclaves
426            .contains(&verified_ect.verified_attestation.quote.identity)
427        {
428            return Err(Error::UnknownEnclave);
429        }
430
431        // Verify allowed endorsement.
432        let node = Self::verify_endorsement(ctx, &cfg.policy, &verified_ect)?;
433
434        // Update registration.
435        let registration = types::Registration {
436            app: body.app,
437            node_id: verified_ect.node_id.unwrap(), // Verified above.
438            entity_id: node.map(|n| n.entity_id),
439            rak: body.ect.capability_tee.rak,
440            rek: body.ect.capability_tee.rek.ok_or(Error::InvalidArgument)?, // REK required.
441            expiration: body.expiration,
442            extra_keys: body.extra_keys,
443            metadata: body.metadata,
444        };
445        state::update_registration(registration)?;
446
447        CurrentState::with(|state| {
448            state.emit_event(Event::InstanceRegistered {
449                app_id: body.app,
450                rak: body.ect.capability_tee.rak.into(),
451            })
452        });
453
454        Ok(())
455    }
456
457    /// Derive a ROFL application-specific key.
458    #[handler(call = "rofl.DeriveKey")]
459    fn tx_derive_key<C: Context>(
460        ctx: &C,
461        body: types::DeriveKey,
462    ) -> Result<types::DeriveKeyResponse, Error> {
463        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_DERIVE_KEY)?;
464
465        // Ensure call is encrypted to avoid leaking any keys by accident.
466        let call_format = CurrentState::with_env(|env| env.tx_call_format());
467        if !call_format.is_encrypted() {
468            return Err(Error::PlainCallFormatNotAllowed);
469        }
470
471        // Currently only generation zero keys are supported.
472        if body.generation != 0 {
473            return Err(Error::InvalidArgument);
474        }
475
476        // Ensure key identifier is not too long.
477        if body.key_id.len() > Cfg::DERIVE_KEY_MAX_KEY_ID_LENGTH {
478            return Err(Error::InvalidArgument);
479        }
480
481        // Disallow invocation from subcalls.
482        if CurrentState::with_env(|env| env.is_internal()) {
483            return Err(Error::Forbidden);
484        }
485
486        if CurrentState::with_env(|env| env.is_check_only()) {
487            return Ok(Default::default());
488        }
489
490        // Ensure caller is an authorized instance of the given application.
491        let reg = Self::get_origin_registration(body.app).ok_or(Error::Forbidden)?;
492
493        // Derive application key.
494        let key = Self::derive_app_key(
495            ctx,
496            &body.app,
497            body.kind,
498            body.scope,
499            &body.key_id,
500            Some(reg),
501        )?;
502        let key = match body.kind {
503            types::KeyKind::EntropyV0 => key.state_key.0.into(),
504            types::KeyKind::X25519 => key.input_keypair.sk.as_ref().into(),
505        };
506
507        Ok(types::DeriveKeyResponse { key })
508    }
509
510    fn derive_app_key_id(
511        app: &app_id::AppId,
512        kind: types::KeyKind,
513        scope: types::KeyScope,
514        key_id: &[u8],
515        reg: Option<types::Registration>,
516    ) -> Result<keymanager::KeyPairId, Error> {
517        // Build the base key identifier.
518        //
519        // We use the following tuple elements which are fed into TupleHash to derive the final key
520        // identifier, in order:
521        //
522        // - V1 context domain separator.
523        // - App ID.
524        // - Encoded kind.
525        // - Key ID.
526        // - Optional CBOR-serialized extra domain separation.
527        //
528        let kind_id = &[kind as u8];
529        let mut key_id = vec![ROFL_DERIVE_KEY_CONTEXT, app.as_ref(), kind_id, key_id];
530        let mut extra_dom: BTreeMap<&str, Vec<u8>> = BTreeMap::new();
531
532        match scope {
533            types::KeyScope::Global => {
534                // Nothing to do here, global keys don't include an explicit scope for backwards
535                // compatibility.
536            }
537            types::KeyScope::Node => {
538                // Fetch node identifier corresponding to the application instance.
539                let node_id = reg.ok_or(Error::InvalidArgument)?.node_id;
540
541                extra_dom.insert("scope", [scope as u8].to_vec());
542                extra_dom.insert("node_id", node_id.as_ref().to_vec());
543            }
544            types::KeyScope::Entity => {
545                // Fetch entity identifier corresponding to the application instance.
546                let entity_id = reg
547                    .ok_or(Error::InvalidArgument)?
548                    .entity_id
549                    .ok_or(Error::InvalidArgument)?;
550
551                extra_dom.insert("scope", [scope as u8].to_vec());
552                extra_dom.insert("entity_id", entity_id.as_ref().to_vec());
553            }
554        };
555
556        // Add optional extra domain separation.
557        let extra_dom = if !extra_dom.is_empty() {
558            cbor::to_vec(extra_dom)
559        } else {
560            vec![]
561        };
562        if !extra_dom.is_empty() {
563            key_id.push(&extra_dom)
564        }
565
566        // Finalize the key identifier.
567        Ok(keymanager::get_key_pair_id(key_id))
568    }
569
570    fn derive_app_key<C: Context>(
571        ctx: &C,
572        app: &app_id::AppId,
573        kind: types::KeyKind,
574        scope: types::KeyScope,
575        key_id: &[u8],
576        reg: Option<types::Registration>,
577    ) -> Result<keymanager::KeyPair, Error> {
578        let key_id = Self::derive_app_key_id(app, kind, scope, key_id, reg)?;
579
580        let km = ctx
581            .key_manager()
582            .ok_or(Error::Abort(dispatcher::Error::KeyManagerFailure(
583                keymanager::KeyManagerError::NotInitialized,
584            )))?;
585        km.get_or_create_keys(key_id)
586            .map_err(|err| Error::Abort(dispatcher::Error::KeyManagerFailure(err)))
587    }
588
589    /// Verify whether the given endorsement is allowed by the application policy.
590    ///
591    /// Returns an optional endorsing node descriptor when available.
592    fn verify_endorsement<C: Context>(
593        ctx: &C,
594        app_policy: &policy::AppAuthPolicy,
595        ect: &VerifiedEndorsedCapabilityTEE,
596    ) -> Result<Option<Node>, Error> {
597        use policy::AllowedEndorsement;
598
599        let endorsing_node_id = ect.node_id.ok_or(Error::UnknownNode)?;
600
601        // Attempt to resolve the node that endorsed the enclave. It may be that the node is not
602        // even registered in the consensus layer which may be acceptable for some policies.
603        let maybe_node = || -> Result<Option<Node>, Error> {
604            let registry = RegistryImmutableState::new(ctx.consensus_state());
605            let node = registry
606                .node(&endorsing_node_id)
607                .map_err(|_| Error::UnknownNode)?;
608            let node = if let Some(node) = node {
609                node
610            } else {
611                return Ok(None);
612            };
613            // Ensure node is not expired.
614            if node.expiration < ctx.epoch() {
615                return Ok(None);
616            }
617
618            Ok(Some(node))
619        }()?;
620
621        // Ensure node is registered for this runtime.
622        let has_runtime = |node: &Node| -> bool {
623            let version = &<C::Runtime as Runtime>::VERSION;
624            node.get_runtime(ctx.runtime_id(), version).is_some()
625        };
626
627        for allowed in &app_policy.endorsements {
628            match (allowed, &maybe_node) {
629                (AllowedEndorsement::Any, _) => {
630                    // Any node is allowed.
631                    return Ok(maybe_node);
632                }
633                (AllowedEndorsement::ComputeRole, Some(node)) => {
634                    if node.has_roles(RolesMask::ROLE_COMPUTE_WORKER) && has_runtime(node) {
635                        return Ok(maybe_node);
636                    }
637                }
638                (AllowedEndorsement::ObserverRole, Some(node)) => {
639                    if node.has_roles(RolesMask::ROLE_OBSERVER) && has_runtime(node) {
640                        return Ok(maybe_node);
641                    }
642                }
643                (AllowedEndorsement::Entity(entity_id), Some(node)) => {
644                    // If a specific entity is required, it may be registered for any runtime.
645                    if &node.entity_id == entity_id {
646                        return Ok(maybe_node);
647                    }
648                }
649                (AllowedEndorsement::Node(node_id), _) => {
650                    if endorsing_node_id == *node_id {
651                        return Ok(maybe_node);
652                    }
653                }
654                _ => continue,
655            }
656        }
657
658        // If nothing matched, this node is not allowed to register.
659        Err(Error::NodeNotAllowed)
660    }
661
662    /// Verify whether the origin transaction is signed by an authorized ROFL instance for the given
663    /// application.
664    #[handler(call = "rofl.IsAuthorizedOrigin", internal)]
665    fn internal_is_authorized_origin<C: Context>(
666        _ctx: &C,
667        app: app_id::AppId,
668    ) -> Result<bool, Error> {
669        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_IS_AUTHORIZED_ORIGIN)?;
670
671        Ok(Self::is_authorized_origin(app))
672    }
673
674    #[handler(call = "rofl.AuthorizedOriginNode", internal)]
675    fn internal_authorized_origin_node<C: Context>(
676        _ctx: &C,
677        app: app_id::AppId,
678    ) -> Result<CorePublicKey, Error> {
679        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_AUTHORIZED_ORIGIN_NODE)?;
680
681        let registration = Self::get_origin_registration(app).ok_or(Error::UnknownInstance)?;
682        Ok(registration.node_id)
683    }
684
685    #[handler(call = "rofl.AuthorizedOriginEntity", internal)]
686    fn internal_authorized_origin_entity<C: Context>(
687        _ctx: &C,
688        app: app_id::AppId,
689    ) -> Result<Option<CorePublicKey>, Error> {
690        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_AUTHORIZED_ORIGIN_ENTITY)?;
691
692        let registration = Self::get_origin_registration(app).ok_or(Error::UnknownInstance)?;
693        Ok(registration.entity_id)
694    }
695
696    /// Returns the configuration for the given ROFL application.
697    #[handler(query = "rofl.App")]
698    fn query_app<C: Context>(_ctx: &C, args: types::AppQuery) -> Result<types::AppConfig, Error> {
699        Self::get_app(args.id)
700    }
701
702    /// Returns all ROFL app configurations.
703    #[handler(query = "rofl.Apps", expensive)]
704    fn query_apps<C: Context>(_ctx: &C, _args: ()) -> Result<Vec<types::AppConfig>, Error> {
705        Self::get_apps()
706    }
707
708    /// Returns a specific registered instance for the given ROFL application.
709    #[handler(query = "rofl.AppInstance")]
710    fn query_app_instance<C: Context>(
711        _ctx: &C,
712        args: types::AppInstanceQuery,
713    ) -> Result<types::Registration, Error> {
714        Self::get_registration(args.app, args.rak)
715    }
716
717    /// Returns a list of all registered instances for the given ROFL application.
718    #[handler(query = "rofl.AppInstances", expensive)]
719    fn query_app_instances<C: Context>(
720        _ctx: &C,
721        args: types::AppQuery,
722    ) -> Result<Vec<types::Registration>, Error> {
723        Self::get_instances(args.id)
724    }
725
726    /// Returns the minimum stake thresholds for managing ROFL.
727    #[handler(query = "rofl.StakeThresholds")]
728    fn query_stake_thresholds<C: Context>(
729        _ctx: &C,
730        _args: (),
731    ) -> Result<types::StakeThresholds, Error> {
732        Ok(types::StakeThresholds {
733            app_create: Cfg::STAKE_APP_CREATE,
734        })
735    }
736
737    /// Returns the minimum stake thresholds for managing ROFL.
738    #[handler(call = "rofl.StakeThresholds", internal)]
739    fn internal_query_stake_thresholds<C: Context>(
740        ctx: &C,
741        _args: (),
742    ) -> Result<types::StakeThresholds, Error> {
743        <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_STAKE_THRESHOLDS)?;
744        Ok(types::StakeThresholds {
745            app_create: Cfg::STAKE_APP_CREATE,
746        })
747    }
748
749    fn resolve_payer_from_tx<C: Context>(
750        ctx: &C,
751        tx: &Transaction,
752        app_policy: &policy::AppAuthPolicy,
753    ) -> Result<Option<Address>, anyhow::Error> {
754        let caller_pk = tx
755            .auth_info
756            .signer_info
757            .first()
758            .and_then(|si| si.address_spec.public_key());
759
760        match tx.call.method.as_str() {
761            "rofl.Register" => {
762                // For registration transactions, extract endorsing node.
763                let body: types::Register = cbor::from_value(tx.call.body.clone())?;
764                if body.expiration <= ctx.epoch() {
765                    return Err(Error::RegistrationExpired.into());
766                }
767
768                // Ensure that the transaction is signed by RAK.
769                let caller_pk = caller_pk.ok_or(Error::NotSignedByRAK)?;
770                if caller_pk != body.ect.capability_tee.rak {
771                    return Err(Error::NotSignedByRAK.into());
772                }
773
774                body.ect.verify_endorsement()?;
775
776                // Checking other details is not relevant for authorizing fee payments as if the
777                // node signed a TEE capability then it is authorizing fees to be spent on its
778                // behalf.
779
780                let node_id = body.ect.node_endorsement.public_key;
781                let payer = Address::from_consensus_pk(&node_id);
782
783                Ok(Some(payer))
784            }
785            _ => {
786                // For others, check if caller is one of the endorsed keys.
787                let caller_pk = match caller_pk {
788                    Some(pk) => pk,
789                    None => return Ok(None),
790                };
791
792                Ok(state::get_endorser(&caller_pk)
793                    .map(|ei| Address::from_consensus_pk(&ei.node_id)))
794            }
795        }
796    }
797}
798
799impl<Cfg: Config> module::FeeProxyHandler for Module<Cfg> {
800    fn resolve_payer<C: Context>(
801        ctx: &C,
802        tx: &Transaction,
803    ) -> Result<Option<Address>, modules::core::Error> {
804        use policy::FeePolicy;
805
806        let proxy = if let Some(ref proxy) = tx.auth_info.fee.proxy {
807            proxy
808        } else {
809            return Ok(None);
810        };
811
812        if proxy.module != MODULE_NAME {
813            return Ok(None);
814        }
815
816        // Look up the per-ROFL app policy.
817        let app_id = app_id::AppId::try_from(proxy.id.as_slice())
818            .map_err(|err| modules::core::Error::InvalidArgument(err.into()))?;
819        let app_policy = state::get_app(app_id).map(|cfg| cfg.policy).ok_or(
820            modules::core::Error::InvalidArgument(Error::UnknownApp.into()),
821        )?;
822
823        match app_policy.fees {
824            FeePolicy::InstancePays => {
825                // Application needs to figure out a way to pay, defer to regular handler.
826                Ok(None)
827            }
828            FeePolicy::EndorsingNodePays => Self::resolve_payer_from_tx(ctx, tx, &app_policy)
829                .map_err(modules::core::Error::InvalidArgument),
830        }
831    }
832}
833
834impl<Cfg: Config> module::TransactionHandler for Module<Cfg> {}
835
836impl<Cfg: Config> module::BlockHandler for Module<Cfg> {
837    fn end_block<C: Context>(ctx: &C) {
838        // Only do work in case the epoch has changed since the last processed block.
839        if !<C::Runtime as Runtime>::Core::has_epoch_changed() {
840            return;
841        }
842
843        // Process enclave expirations.
844        // TODO: Consider processing unprocessed in the next block(s) if there are too many.
845        state::expire_registrations(ctx.epoch(), 128);
846    }
847}
848
849impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}