oasis_runtime_sdk/modules/rofl/app/
mod.rs

1//! Wrapper to make development of ROFL components easier.
2use std::{collections::BTreeMap, sync::Arc};
3
4use anyhow::Result;
5use async_trait::async_trait;
6use base64::prelude::*;
7use tokio::sync::mpsc;
8
9use crate::{
10    core::{
11        app,
12        common::version,
13        config::Config,
14        consensus::{roothash, verifier::TrustRoot},
15        dispatcher::{PostInitState, PreInitState},
16        start_runtime,
17    },
18    crypto,
19    types::transaction,
20};
21
22pub mod client;
23mod env;
24pub mod init;
25mod notifier;
26pub mod prelude;
27mod processor;
28mod registration;
29mod watchdog;
30
31pub use crate::modules::rofl::app_id::AppId;
32pub use client::Client;
33pub use env::Environment;
34
35/// ROFL component application.
36#[allow(unused_variables)]
37#[async_trait]
38pub trait App: Send + Sync + 'static {
39    /// ROFL application version.
40    const VERSION: version::Version;
41
42    /// Identifier of the application (used for registrations).
43    fn id() -> AppId {
44        // By default we fetch the application identifier from the build-time environment.
45        #[allow(clippy::option_env_unwrap)]
46        AppId::from_bech32(
47            option_env!("ROFL_APP_ID").expect("Override App::id or specify ROFL_APP_ID."),
48        )
49        .expect("Corrupted ROFL_APP_ID (must be Bech32-encoded ROFL app ID).")
50    }
51
52    /// Return the consensus layer trust root for this runtime; if `None`, consensus layer integrity
53    /// verification will not be performed.
54    fn consensus_trust_root() -> Option<TrustRoot> {
55        // By default we fetch the trust root from the build-time environment.
56        option_env!("ROFL_CONSENSUS_TRUST_ROOT").map(|raw_trust_root| {
57            // Parse from base64-encoded CBOR.
58            cbor::from_slice(
59                &BASE64_STANDARD
60                    .decode(raw_trust_root)
61                    .expect("Corrupted ROFL_CONSENSUS_TRUST_ROOT (must be Base64-encoded CBOR)."),
62            )
63            .expect("Corrupted ROFL_CONSENSUS_TRUST_ROOT (must be Base64-encoded CBOR).")
64        })
65    }
66
67    /// Create a new unsigned transaction.
68    fn new_transaction<B>(&self, method: &str, body: B) -> transaction::Transaction
69    where
70        B: cbor::Encode,
71    {
72        let mut tx = transaction::Transaction::new(method, body);
73        // Make the ROFL module resolve the payer for all of our transactions.
74        tx.set_fee_proxy("rofl", Self::id().as_ref());
75        tx
76    }
77
78    /// Fetches custom app instance metadata that is included in its on-chain registration.
79    ///
80    /// This method is called before each registration refresh. Returning an error will not block
81    /// registration, rather it will result in the metadata being cleared.
82    async fn get_metadata(
83        self: Arc<Self>,
84        env: Environment<Self>,
85    ) -> Result<BTreeMap<String, String>>
86    where
87        Self: Sized,
88    {
89        Ok(BTreeMap::new())
90    }
91
92    /// Custom post-registration initialization. It runs before any image-specific scripts are
93    /// called by the runtime so it can be used to do things like set up custom storage after
94    /// successful registration.
95    ///
96    /// Until this function completes, no further initialization will happen.
97    async fn post_registration_init(self: Arc<Self>, env: Environment<Self>)
98    where
99        Self: Sized,
100    {
101        // Default implementation just runs the trivial initialization.
102        init::post_registration_init();
103    }
104
105    /// Main application processing loop.
106    async fn run(self: Arc<Self>, env: Environment<Self>)
107    where
108        Self: Sized,
109    {
110        // Default implementation does nothing.
111    }
112
113    /// Logic that runs on each runtime block. Only one of these will run concurrently.
114    async fn on_runtime_block(self: Arc<Self>, env: Environment<Self>, round: u64)
115    where
116        Self: Sized,
117    {
118        // Default implementation does nothing.
119    }
120
121    /// Start the application.
122    fn start(self)
123    where
124        Self: Sized,
125    {
126        start_runtime(
127            Box::new(|state: PreInitState<'_>| -> PostInitState {
128                // Fetch host information and configure domain separation context.
129                let hi = state.protocol.get_host_info();
130                crypto::signature::context::set_chain_context(
131                    hi.runtime_id,
132                    &hi.consensus_chain_context,
133                );
134
135                PostInitState {
136                    app: Some(Box::new(AppWrapper::new(self, &state))),
137                    ..Default::default()
138                }
139            }),
140            Config {
141                version: Self::VERSION,
142                trust_root: Self::consensus_trust_root(),
143                ..Default::default()
144            },
145        );
146    }
147}
148
149struct AppWrapper {
150    cmdq: mpsc::Sender<processor::Command>,
151}
152
153impl AppWrapper {
154    fn new<A>(app: A, state: &PreInitState<'_>) -> Self
155    where
156        A: App,
157    {
158        Self {
159            cmdq: processor::Processor::start(app, state),
160        }
161    }
162}
163
164#[async_trait]
165impl app::App for AppWrapper {
166    async fn on_runtime_block(&self, blk: &roothash::AnnotatedBlock) -> Result<()> {
167        self.cmdq
168            .send(processor::Command::ProcessRuntimeBlock(blk.clone()))
169            .await?;
170        Ok(())
171    }
172
173    async fn on_runtime_event(
174        &self,
175        _blk: &roothash::AnnotatedBlock,
176        _tags: &[Vec<u8>],
177    ) -> Result<()> {
178        Ok(())
179    }
180}