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