openmina_node_web/
lib.rs

1#![cfg(target_family = "wasm")]
2
3use ::node::transition_frontier::genesis::GenesisConfig;
4pub use openmina_node_common::*;
5
6mod rayon;
7pub use rayon::init_rayon;
8
9mod node;
10pub use node::{Node, NodeBuilder};
11
12use ::node::{
13    account::AccountSecretKey,
14    core::thread,
15    snark::{BlockVerifier, TransactionVerifier},
16};
17use anyhow::Context;
18use gloo_utils::format::JsValueSerdeExt;
19use ledger::proofs::provers::BlockProver;
20use openmina_node_common::rpc::RpcSender;
21use wasm_bindgen::prelude::*;
22
23use crate::node::P2pTaskRemoteSpawner;
24
25/// Automatically run after wasm is loaded.
26#[wasm_bindgen(start)]
27fn main() {
28    thread::main_thread_init();
29    wasm_bindgen_futures::spawn_local(async {
30        console_error_panic_hook::set_once();
31        tracing::initialize(tracing::Level::INFO);
32
33        init_rayon().await.unwrap();
34    });
35}
36
37#[wasm_bindgen]
38pub fn build_env() -> JsValue {
39    JsValue::from_serde(&::node::BuildEnv::get()).unwrap_or_default()
40}
41
42fn parse_bp_key(key: JsValue) -> Option<AccountSecretKey> {
43    if key.is_falsy() {
44        return None;
45    }
46
47    if key.is_string() {
48        return Some(
49            key.as_string()
50                .unwrap()
51                .parse()
52                .expect("failed to parse passed block producer keys"),
53        );
54    }
55
56    let (encrypted, password) = if key.is_array() {
57        let arr: js_sys::Array = key.into();
58        let password = arr
59            .at(1)
60            .as_string()
61            .expect("invalid block_producer password");
62        let encrypted = arr
63            .at(0)
64            .into_serde()
65            .expect("block_producer encrypted key decode failed");
66        (encrypted, password)
67    } else {
68        panic!("unsupported block_producer keys type: {key:?}");
69    };
70
71    Some(
72        AccountSecretKey::from_encrypted(&encrypted, &password)
73            .expect("block_producer secret key decrypt failed"),
74    )
75}
76
77#[wasm_bindgen]
78pub async fn run(
79    block_producer: JsValue,
80    seed_nodes_url: Option<String>,
81    genesis_config_url: Option<String>,
82) -> RpcSender {
83    let block_producer = parse_bp_key(block_producer);
84
85    let (rpc_sender_tx, rpc_sender_rx) = ::node::core::channels::oneshot::channel();
86    let _ = thread::spawn(move || {
87        wasm_bindgen_futures::spawn_local(async move {
88            let mut node = setup_node(block_producer, seed_nodes_url, genesis_config_url).await;
89            let _ = rpc_sender_tx.send(node.rpc());
90            node.run_forever().await;
91        });
92
93        keep_worker_alive_cursed_hack();
94    });
95
96    rpc_sender_rx.await.unwrap()
97}
98
99async fn setup_node(
100    block_producer: Option<AccountSecretKey>,
101    seed_nodes_url: Option<String>,
102    genesis_config_url: Option<String>,
103) -> openmina_node_common::Node<NodeService> {
104    let block_verifier_index = BlockVerifier::make().await;
105    let work_verifier_index = TransactionVerifier::make().await;
106
107    let genesis_config = if let Some(genesis_config_url) = genesis_config_url {
108        let bytes = ::node::core::http::get_bytes(&genesis_config_url)
109            .await
110            .expect("failed to fetch genesis config");
111        GenesisConfig::Prebuilt(bytes.into()).into()
112    } else {
113        ::node::config::DEVNET_CONFIG.clone()
114    };
115
116    let mut node_builder: NodeBuilder = NodeBuilder::new(None, genesis_config);
117    node_builder
118        .block_verifier_index(block_verifier_index.clone())
119        .work_verifier_index(work_verifier_index.clone());
120
121    // TODO(binier): refactor
122    if let Some(seed_nodes_url) = seed_nodes_url {
123        let peers = ::node::core::http::get_bytes(&seed_nodes_url)
124            .await
125            .expect("failed to fetch seed nodes");
126        node_builder.initial_peers(
127            String::from_utf8_lossy(&peers)
128                .split("\n")
129                .filter(|s| !s.trim().is_empty())
130                .map(|s| s.trim().parse().expect("failed to parse seed node addr")),
131        );
132    }
133
134    if let Some(bp_key) = block_producer {
135        thread::spawn(move || {
136            BlockProver::make(Some(block_verifier_index), Some(work_verifier_index));
137        });
138        node_builder.block_producer(bp_key, None);
139    }
140
141    node_builder
142        .p2p_custom_task_spawner(P2pTaskRemoteSpawner {})
143        .unwrap();
144    node_builder.gather_stats();
145    node_builder.build().context("node build failed!").unwrap()
146}
147
148fn keep_worker_alive_cursed_hack() {
149    wasm_bindgen::throw_str("Cursed hack to keep workers alive. See https://github.com/rustwasm/wasm-bindgen/issues/2945");
150}