cuprated/rpc/
server.rs

1//! RPC server initialization and main loop.
2
3use std::{
4    net::{IpAddr, SocketAddr},
5    time::Duration,
6};
7
8use anyhow::Error;
9use tokio::net::TcpListener;
10use tower::limit::rate::RateLimitLayer;
11use tower_http::limit::RequestBodyLimitLayer;
12use tracing::{info, warn};
13
14use cuprate_blockchain::service::BlockchainReadHandle;
15use cuprate_consensus::BlockchainContextService;
16use cuprate_helper::network::Network;
17use cuprate_rpc_interface::{RouterBuilder, RpcHandler};
18use cuprate_txpool::service::TxpoolReadHandle;
19
20use crate::{
21    config::{restricted_rpc_port, unrestricted_rpc_port, RpcConfig},
22    rpc::{rpc_handler::BlockchainManagerHandle, CupratedRpcHandler},
23    txpool::IncomingTxHandler,
24};
25
26/// Initialize the RPC server(s).
27///
28/// # Panics
29/// This function will panic if:
30/// - the server(s) could not be started
31/// - unrestricted RPC is started on non-local
32///   address without override option
33pub fn init_rpc_servers(
34    config: RpcConfig,
35    network: Network,
36    blockchain_read: BlockchainReadHandle,
37    blockchain_context: BlockchainContextService,
38    txpool_read: TxpoolReadHandle,
39    tx_handler: IncomingTxHandler,
40) {
41    for ((enable, addr, port, request_byte_limit), restricted) in [
42        (
43            (
44                config.unrestricted.enable,
45                config.unrestricted.address,
46                unrestricted_rpc_port(config.unrestricted.port, network),
47                config.unrestricted.request_byte_limit,
48            ),
49            false,
50        ),
51        (
52            (
53                config.restricted.enable,
54                config.restricted.address,
55                restricted_rpc_port(config.restricted.port, network),
56                config.restricted.request_byte_limit,
57            ),
58            true,
59        ),
60    ] {
61        if !enable {
62            info!(restricted, "Skipping RPC server");
63            continue;
64        }
65
66        if !restricted && !cuprate_helper::net::ip_is_local(addr) {
67            if config
68                .unrestricted
69                .i_know_what_im_doing_allow_public_unrestricted_rpc
70            {
71                warn!(
72                    address = %addr,
73                    "Starting unrestricted RPC on non-local address, this is dangerous!"
74                );
75            } else {
76                panic!("Refusing to start unrestricted RPC on a non-local address ({addr})");
77            }
78        }
79
80        let rpc_handler = CupratedRpcHandler::new(
81            restricted,
82            blockchain_read.clone(),
83            blockchain_context.clone(),
84            txpool_read.clone(),
85            tx_handler.clone(),
86        );
87
88        tokio::task::spawn(async move {
89            run_rpc_server(
90                rpc_handler,
91                restricted,
92                SocketAddr::new(addr, port),
93                request_byte_limit,
94            )
95            .await
96            .unwrap();
97        });
98    }
99}
100
101/// This initializes and runs an RPC server.
102///
103/// The function will only return when the server itself returns or an error occurs.
104async fn run_rpc_server(
105    rpc_handler: CupratedRpcHandler,
106    restricted: bool,
107    address: SocketAddr,
108    request_byte_limit: usize,
109) -> Result<(), Error> {
110    info!(
111        restricted,
112        address = %address,
113        "Starting RPC server"
114    );
115
116    // TODO:
117    // - add functions that are `all()` but for restricted RPC
118    // - enable aliases automatically `other_get_height` + `other_getheight`?
119    let router = RouterBuilder::new()
120        .json_rpc()
121        .other_get_height()
122        .other_send_raw_transaction()
123        .other_sendrawtransaction()
124        .fallback()
125        .build()
126        .with_state(rpc_handler);
127
128    // Add restrictive layers if restricted RPC.
129    //
130    // TODO: <https://github.com/Cuprate/cuprate/issues/445>
131    let router = if request_byte_limit != 0 {
132        router.layer(RequestBodyLimitLayer::new(request_byte_limit))
133    } else {
134        router
135    };
136
137    // Start the server.
138    //
139    // TODO: impl custom server code, don't use axum.
140    let listener = TcpListener::bind(address).await?;
141    axum::serve(listener, router).await?;
142
143    Ok(())
144}