1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use super::{
    background::{BackgroundTask, FromSubxt, MethodResponse},
    LightClientRpcError,
};
use serde_json::value::RawValue;
use tokio::sync::{mpsc, mpsc::error::SendError, oneshot};

use super::platform::build_platform;

pub const LOG_TARGET: &str = "light-client";

/// The light-client RPC implementation that is used to connect with the chain.
#[derive(Clone)]
pub struct LightClientRpc {
    /// Communicate with the backend task that multiplexes the responses
    /// back to the frontend.
    to_backend: mpsc::UnboundedSender<FromSubxt>,
}

impl LightClientRpc {
    /// Constructs a new [`LightClientRpc`], providing the chain specification.
    ///
    /// The chain specification can be downloaded from a trusted network via
    /// the `sync_state_genSyncSpec` RPC method. This parameter expects the
    /// chain spec in text format (ie not in hex-encoded scale-encoded as RPC methods
    /// will provide).
    ///
    /// ## Panics
    ///
    /// Panics if being called outside of `tokio` runtime context.
    pub fn new(
        config: smoldot_light::AddChainConfig<'_, (), impl Iterator<Item = smoldot_light::ChainId>>,
    ) -> Result<LightClientRpc, LightClientRpcError> {
        tracing::trace!(target: LOG_TARGET, "Create light client");

        let mut client = smoldot_light::Client::new(build_platform());

        let smoldot_light::AddChainSuccess {
            chain_id,
            json_rpc_responses,
        } = client
            .add_chain(config)
            .map_err(|err| LightClientRpcError::AddChainError(err.to_string()))?;

        let (to_backend, backend) = mpsc::unbounded_channel();

        // `json_rpc_responses` can only be `None` if we had passed `json_rpc: Disabled`.
        let rpc_responses = json_rpc_responses.expect("Light client RPC configured; qed");

        let future = async move {
            let mut task = BackgroundTask::new(client, chain_id);
            task.start_task(backend, rpc_responses).await;
        };

        #[cfg(feature = "native")]
        tokio::spawn(future);
        #[cfg(feature = "web")]
        wasm_bindgen_futures::spawn_local(future);

        Ok(LightClientRpc { to_backend })
    }

    /// Submits an RPC method request to the light-client.
    ///
    /// This method sends a request to the light-client to execute an RPC method with the provided parameters.
    /// The parameters are parsed into a valid JSON object in the background.
    pub fn method_request(
        &self,
        method: String,
        params: String,
    ) -> Result<oneshot::Receiver<MethodResponse>, SendError<FromSubxt>> {
        let (sender, receiver) = oneshot::channel();

        self.to_backend.send(FromSubxt::Request {
            method,
            params,
            sender,
        })?;

        Ok(receiver)
    }

    /// Makes an RPC subscription call to the light-client.
    ///
    /// This method sends a request to the light-client to establish an RPC subscription with the provided parameters.
    /// The parameters are parsed into a valid JSON object in the background.
    pub fn subscription_request(
        &self,
        method: String,
        params: String,
    ) -> Result<
        (
            oneshot::Receiver<MethodResponse>,
            mpsc::UnboundedReceiver<Box<RawValue>>,
        ),
        SendError<FromSubxt>,
    > {
        let (sub_id, sub_id_rx) = oneshot::channel();
        let (sender, receiver) = mpsc::unbounded_channel();

        self.to_backend.send(FromSubxt::Subscription {
            method,
            params,
            sub_id,
            sender,
        })?;

        Ok((sub_id_rx, receiver))
    }
}