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