1use 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
36const MODULE_NAME: &str = "rofl";
38
39pub use config::Config;
40pub use error::Error;
41pub use event::Event;
42
43#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
45pub struct Parameters {}
46
47#[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#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
61pub struct Genesis {
62 pub parameters: Parameters,
63
64 pub apps: Vec<types::AppConfig>,
66}
67
68pub trait API {
70 fn get_origin_rak() -> Option<PublicKey>;
77
78 fn get_origin_registration(app: app_id::AppId) -> Option<types::Registration>;
85
86 fn is_authorized_origin(app: app_id::AppId) -> bool;
93
94 fn get_registration(app: app_id::AppId, rak: PublicKey) -> Result<types::Registration, Error>;
96
97 fn get_app(id: app_id::AppId) -> Result<types::AppConfig, Error>;
99
100 fn get_apps() -> Result<Vec<types::AppConfig>, Error>;
102
103 fn get_instances(id: app_id::AppId) -> Result<Vec<types::Registration>, Error>;
105}
106
107pub static ADDRESS_APP_STAKE_POOL: Lazy<Address> =
111 Lazy::new(|| Address::from_module(MODULE_NAME, "app-stake-pool"));
112
113pub static ROFL_DERIVE_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/rofl: derive key v1";
115pub 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 state::get_endorser(&caller_pk).map(|kei| match kei {
128 state::KeyEndorsementInfo { rak: Some(rak), .. } => rak.into(),
130 _ => 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 Self::set_params(genesis.parameters);
179
180 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 #[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 if state::get_app(app_id).is_some() {
228 return Err(Error::AppAlreadyExists);
229 }
230
231 <C::Runtime as Runtime>::Accounts::transfer(
233 creator,
234 *ADDRESS_APP_STAKE_POOL,
235 &Cfg::STAKE_APP_CREATE,
236 )?;
237
238 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 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 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 #[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 Self::ensure_caller_is_admin(&cfg)?;
308
309 if CurrentState::with_env(|env| env.is_check_only()) {
310 return Ok(());
311 }
312
313 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 #[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 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 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 #[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 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 let verified_ect = body
418 .ect
419 .verify(&cfg.policy.quotes)
420 .map_err(|_| Error::InvalidArgument)?;
421
422 if !cfg
424 .policy
425 .enclaves
426 .contains(&verified_ect.verified_attestation.quote.identity)
427 {
428 return Err(Error::UnknownEnclave);
429 }
430
431 let node = Self::verify_endorsement(ctx, &cfg.policy, &verified_ect)?;
433
434 let registration = types::Registration {
436 app: body.app,
437 node_id: verified_ect.node_id.unwrap(), 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)?, 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 #[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 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 if body.generation != 0 {
473 return Err(Error::InvalidArgument);
474 }
475
476 if body.key_id.len() > Cfg::DERIVE_KEY_MAX_KEY_ID_LENGTH {
478 return Err(Error::InvalidArgument);
479 }
480
481 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 let reg = Self::get_origin_registration(body.app).ok_or(Error::Forbidden)?;
492
493 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 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 }
537 types::KeyScope::Node => {
538 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 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 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 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 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 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 if node.expiration < ctx.epoch() {
615 return Ok(None);
616 }
617
618 Ok(Some(node))
619 }()?;
620
621 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 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 &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 Err(Error::NodeNotAllowed)
660 }
661
662 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 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 if !<C::Runtime as Runtime>::Core::has_epoch_changed() {
840 return;
841 }
842
843 state::expire_registrations(ctx.epoch(), 128);
846 }
847}
848
849impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}