mwc_api/
handlers.rs

1// Copyright 2019 The Grin Developers
2// Copyright 2024 The MWC Developers
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16pub mod blocks_api;
17pub mod chain_api;
18pub mod peers_api;
19pub mod pool_api;
20pub mod server_api;
21pub mod transactions_api;
22pub mod utils;
23pub mod version_api;
24
25use self::blocks_api::BlockHandler;
26use self::blocks_api::HeaderHandler;
27use self::chain_api::ChainCompactHandler;
28use self::chain_api::ChainHandler;
29use self::chain_api::ChainValidationHandler;
30use self::chain_api::KernelHandler;
31use self::chain_api::OutputHandler;
32use self::peers_api::PeerHandler;
33use self::peers_api::PeersAllHandler;
34use self::peers_api::PeersConnectedHandler;
35use self::pool_api::PoolInfoHandler;
36use self::pool_api::PoolPushHandler;
37use self::server_api::IndexHandler;
38use self::server_api::StatusHandler;
39use self::transactions_api::TxHashSetHandler;
40use self::version_api::VersionHandler;
41use crate::auth::{
42	BasicAuthMiddleware, BasicAuthURIMiddleware, MWC_BASIC_REALM, MWC_FOREIGN_BASIC_REALM,
43};
44use crate::chain;
45use crate::chain::{Chain, SyncState};
46use crate::core::global;
47use crate::core::stratum;
48use crate::foreign::Foreign;
49use crate::foreign_rpc::ForeignRpc;
50use crate::owner::Owner;
51use crate::owner_rpc::OwnerRpc;
52use crate::p2p;
53use crate::pool;
54use crate::pool::{BlockChain, PoolAdapter};
55use crate::rest::{ApiServer, Error, TLSConfig};
56use crate::router::ResponseFuture;
57use crate::router::{Router, RouterError};
58use crate::stratum::Stratum;
59use crate::stratum_rpc::StratumRpc;
60use crate::util::to_base64;
61use crate::util::RwLock;
62use crate::util::StopState;
63use crate::web::*;
64use easy_jsonrpc_mw::{Handler, MaybeReply};
65use futures::channel::oneshot;
66use hyper::{Body, Request, Response, StatusCode};
67use serde::Serialize;
68use std::net::SocketAddr;
69use std::sync::{Arc, Weak};
70use std::thread;
71
72/// Listener version, providing same API but listening for requests on a
73/// port and wrapping the calls
74pub fn node_apis<B, P>(
75	addr: &str,
76	chain: Arc<chain::Chain>,
77	tx_pool: Arc<RwLock<pool::TransactionPool<B, P>>>,
78	peers: Arc<p2p::Peers>,
79	sync_state: Arc<chain::SyncState>,
80	api_secret: Option<String>,
81	foreign_api_secret: Option<String>,
82	tls_config: Option<TLSConfig>,
83	allow_to_stop: bool,
84	stratum_ip_pool: Arc<stratum::connections::StratumIpPool>,
85	api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>),
86	stop_state: Arc<StopState>,
87) -> Result<(), Error>
88where
89	B: BlockChain + 'static,
90	P: PoolAdapter + 'static,
91{
92	// Adding legacy owner v1 API
93	let mut router = build_router(
94		chain.clone(),
95		tx_pool.clone(),
96		peers.clone(),
97		sync_state.clone(),
98		allow_to_stop,
99	)
100	.expect("unable to build API router");
101
102	let basic_auth_key = if global::is_mainnet() {
103		"mwcmain"
104	} else if global::is_floonet() {
105		"mwcfloo"
106	} else {
107		"mwc"
108	};
109
110	// Add basic auth to v1 API and owner v2 API
111	if let Some(api_secret) = api_secret {
112		let api_basic_auth = format!(
113			"Basic {}",
114			to_base64(&format!("{}:{}", basic_auth_key, api_secret))
115		);
116		let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(
117			api_basic_auth,
118			&MWC_BASIC_REALM,
119			Some("/v2/foreign".into()),
120		));
121		router.add_middleware(basic_auth_middleware);
122	}
123
124	let api_handler = OwnerAPIHandlerV2::new(
125		Arc::downgrade(&chain),
126		Arc::downgrade(&peers),
127		Arc::downgrade(&sync_state),
128	);
129	router.add_route("/v2/owner", Arc::new(api_handler))?;
130
131	let stratum_handler_v2 = StratumAPIHandlerV2::new(stratum_ip_pool);
132	router.add_route("/v2/stratum", Arc::new(stratum_handler_v2))?;
133
134	// Add basic auth to v2 foreign API only
135	if let Some(api_secret) = foreign_api_secret {
136		let api_basic_auth = format!(
137			"Basic {}",
138			to_base64(&format!("{}:{}", basic_auth_key, api_secret))
139		);
140		let basic_auth_middleware = Arc::new(BasicAuthURIMiddleware::new(
141			api_basic_auth,
142			&MWC_FOREIGN_BASIC_REALM,
143			"/v2/foreign".into(),
144		));
145		router.add_middleware(basic_auth_middleware);
146	}
147
148	let api_handler = ForeignAPIHandlerV2::new(
149		Arc::downgrade(&peers),
150		Arc::downgrade(&chain),
151		Arc::downgrade(&tx_pool),
152		Arc::downgrade(&sync_state),
153	);
154	router.add_route("/v2/foreign", Arc::new(api_handler))?;
155
156	let mut apis = ApiServer::new();
157	warn!("Starting HTTP Node APIs server at {}.", addr);
158	let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
159	let api_thread = apis.start(socket_addr, router, tls_config, api_chan);
160
161	warn!("HTTP Node listener started.");
162
163	thread::Builder::new()
164		.name("api_monitor".to_string())
165		.spawn(move || {
166			// monitor for stop state is_stopped
167			loop {
168				std::thread::sleep(std::time::Duration::from_millis(100));
169				if stop_state.is_stopped() {
170					apis.stop();
171					break;
172				}
173			}
174		})
175		.ok();
176
177	match api_thread {
178		Ok(_) => Ok(()),
179		Err(e) => {
180			error!("HTTP API server failed to start. Err: {}", e);
181			Err(Error::Internal(format!(
182				"HTTP API server failed to start, {}",
183				e
184			)))
185		}
186	}
187}
188
189/// V2 API Handler/Wrapper for owner functions
190pub struct OwnerAPIHandlerV2 {
191	pub chain: Weak<Chain>,
192	pub peers: Weak<p2p::Peers>,
193	pub sync_state: Weak<SyncState>,
194}
195
196impl OwnerAPIHandlerV2 {
197	/// Create a new owner API handler for GET methods
198	pub fn new(chain: Weak<Chain>, peers: Weak<p2p::Peers>, sync_state: Weak<SyncState>) -> Self {
199		OwnerAPIHandlerV2 {
200			chain,
201			peers,
202			sync_state,
203		}
204	}
205}
206
207impl crate::router::Handler for OwnerAPIHandlerV2 {
208	fn post(&self, req: Request<Body>) -> ResponseFuture {
209		let api = Owner::new(
210			self.chain.clone(),
211			self.peers.clone(),
212			self.sync_state.clone(),
213		);
214
215		Box::pin(async move {
216			match parse_body(req).await {
217				Ok(val) => {
218					let owner_api = &api as &dyn OwnerRpc;
219					let res = match owner_api.handle_request(val) {
220						MaybeReply::Reply(r) => r,
221						MaybeReply::DontReply => {
222							// Since it's http, we need to return something. We return [] because jsonrpc
223							// clients will parse it as an empty batch response.
224							serde_json::json!([])
225						}
226					};
227					Ok(json_response_pretty(&res))
228				}
229				Err(e) => {
230					error!("Request Error: {:?}", e);
231					Ok(create_error_response(e))
232				}
233			}
234		})
235	}
236
237	fn options(&self, _req: Request<Body>) -> ResponseFuture {
238		Box::pin(async { Ok(create_ok_response("{}")) })
239	}
240}
241
242/// V2 API Handler/Wrapper for foreign functions
243pub struct ForeignAPIHandlerV2<B, P>
244where
245	B: BlockChain,
246	P: PoolAdapter,
247{
248	pub peers: Weak<mwc_p2p::Peers>,
249	pub chain: Weak<Chain>,
250	pub tx_pool: Weak<RwLock<pool::TransactionPool<B, P>>>,
251	pub sync_state: Weak<SyncState>,
252}
253
254impl<B, P> ForeignAPIHandlerV2<B, P>
255where
256	B: BlockChain,
257	P: PoolAdapter,
258{
259	/// Create a new foreign API handler for GET methods
260	pub fn new(
261		peers: Weak<mwc_p2p::Peers>,
262		chain: Weak<Chain>,
263		tx_pool: Weak<RwLock<pool::TransactionPool<B, P>>>,
264		sync_state: Weak<SyncState>,
265	) -> Self {
266		ForeignAPIHandlerV2 {
267			peers,
268			chain,
269			tx_pool,
270			sync_state,
271		}
272	}
273}
274
275impl<B, P> crate::router::Handler for ForeignAPIHandlerV2<B, P>
276where
277	B: BlockChain + 'static,
278	P: PoolAdapter + 'static,
279{
280	fn post(&self, req: Request<Body>) -> ResponseFuture {
281		let api = Foreign::new(
282			self.peers.clone(),
283			self.chain.clone(),
284			self.tx_pool.clone(),
285			self.sync_state.clone(),
286		);
287
288		Box::pin(async move {
289			match parse_body(req).await {
290				Ok(val) => {
291					let foreign_api = &api as &dyn ForeignRpc;
292					let res = match foreign_api.handle_request(val) {
293						MaybeReply::Reply(r) => r,
294						MaybeReply::DontReply => {
295							// Since it's http, we need to return something. We return [] because jsonrpc
296							// clients will parse it as an empty batch response.
297							serde_json::json!([])
298						}
299					};
300					Ok(json_response_pretty(&res))
301				}
302				Err(e) => {
303					error!("Request Error: {:?}", e);
304					Ok(create_error_response(e))
305				}
306			}
307		})
308	}
309
310	fn options(&self, _req: Request<Body>) -> ResponseFuture {
311		Box::pin(async { Ok(create_ok_response("{}")) })
312	}
313}
314
315/// V2 API Handler/Wrapper for stratum
316pub struct StratumAPIHandlerV2 {
317	stratum_ip_pool: Arc<stratum::connections::StratumIpPool>,
318}
319
320impl StratumAPIHandlerV2 {
321	/// Create a new owner API handler for GET methods
322	pub fn new(stratum_ip_pool: Arc<stratum::connections::StratumIpPool>) -> Self {
323		StratumAPIHandlerV2 { stratum_ip_pool }
324	}
325}
326
327impl crate::router::Handler for StratumAPIHandlerV2 {
328	fn post(&self, req: Request<Body>) -> ResponseFuture {
329		let api = Stratum::new(self.stratum_ip_pool.clone());
330
331		Box::pin(async move {
332			match parse_body(req).await {
333				Ok(val) => {
334					let stratum_api = &api as &dyn StratumRpc;
335					let res = match stratum_api.handle_request(val) {
336						MaybeReply::Reply(r) => r,
337						MaybeReply::DontReply => {
338							// Since it's http, we need to return something. We return [] because jsonrpc
339							// clients will parse it as an empty batch response.
340							serde_json::json!([])
341						}
342					};
343					Ok(json_response_pretty(&res))
344				}
345				Err(e) => {
346					error!("Request Error: {:?}", e);
347					Ok(create_error_response(e))
348				}
349			}
350		})
351	}
352
353	fn options(&self, _req: Request<Body>) -> ResponseFuture {
354		Box::pin(async { Ok(create_ok_response("{}")) })
355	}
356}
357
358// pretty-printed version of above
359fn json_response_pretty<T>(s: &T) -> Response<Body>
360where
361	T: Serialize,
362{
363	match serde_json::to_string_pretty(s) {
364		Ok(json) => response(StatusCode::OK, json),
365		Err(e) => response(
366			StatusCode::INTERNAL_SERVER_ERROR,
367			format!("{{\"error\": \"{}\"}}", e),
368		),
369	}
370}
371
372fn create_error_response(e: Error) -> Response<Body> {
373	Response::builder()
374		.status(StatusCode::INTERNAL_SERVER_ERROR)
375		.header("access-control-allow-origin", "*")
376		.header(
377			"access-control-allow-headers",
378			"Content-Type, Authorization",
379		)
380		.body(format!("{}", e).into())
381		.unwrap()
382}
383
384fn create_ok_response(json: &str) -> Response<Body> {
385	Response::builder()
386		.status(StatusCode::OK)
387		.header("access-control-allow-origin", "*")
388		.header(
389			"access-control-allow-headers",
390			"Content-Type, Authorization",
391		)
392		.header(hyper::header::CONTENT_TYPE, "application/json")
393		.body(json.to_string().into())
394		.unwrap()
395}
396
397/// Build a new hyper Response with the status code and body provided.
398///
399/// Whenever the status code is `StatusCode::OK` the text parameter should be
400/// valid JSON as the content type header will be set to `application/json'
401fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
402	let mut builder = Response::builder();
403
404	builder = builder
405		.status(status)
406		.header("access-control-allow-origin", "*")
407		.header(
408			"access-control-allow-headers",
409			"Content-Type, Authorization",
410		);
411
412	if status == StatusCode::OK {
413		builder = builder.header(hyper::header::CONTENT_TYPE, "application/json");
414	}
415
416	builder.body(text.into()).unwrap()
417}
418
419// Legacy V1 router
420/*#[deprecated(
421	since = "4.0.0",
422	note = "The V1 Node API will be removed in mwc 5.0.0. Please migrate to the V2 API as soon as possible."
423)]*/
424pub fn build_router<B, P>(
425	chain: Arc<chain::Chain>,
426	tx_pool: Arc<RwLock<pool::TransactionPool<B, P>>>,
427	peers: Arc<p2p::Peers>,
428	sync_state: Arc<chain::SyncState>,
429	allow_to_stop: bool,
430) -> Result<Router, RouterError>
431where
432	B: BlockChain + 'static,
433	P: PoolAdapter + 'static,
434{
435	let route_list = vec![
436		"get blocks".to_string(),
437		"get headers".to_string(),
438		"get chain".to_string(),
439		"post chain/compact".to_string(),
440		"get chain/validate".to_string(),
441		"get chain/kernels/xxx?min_height=yyy&max_height=zzz".to_string(),
442		"get chain/outputs/byids?id=xxx,yyy,zzz".to_string(),
443		"get chain/outputs/byheight?start_height=101&end_height=200".to_string(),
444		"get status".to_string(),
445		"get txhashset/roots".to_string(),
446		"get txhashset/lastoutputs?n=10".to_string(),
447		"get txhashset/lastrangeproofs".to_string(),
448		"get txhashset/lastkernels".to_string(),
449		"get txhashset/outputs?start_index=1&max=100".to_string(),
450		"get txhashset/merkleproof?n=1".to_string(),
451		"get pool".to_string(),
452		"post pool/push_tx".to_string(),
453		"post peers/a.b.c.d:p/ban".to_string(),
454		"post peers/a.b.c.d:p/unban".to_string(),
455		"get peers/all".to_string(),
456		"get peers/connected".to_string(),
457		"get peers/a.b.c.d".to_string(),
458		"get version".to_string(),
459	];
460	let index_handler = IndexHandler { list: route_list };
461
462	let output_handler = OutputHandler {
463		chain: Arc::downgrade(&chain),
464	};
465	let kernel_handler = KernelHandler {
466		chain: Arc::downgrade(&chain),
467	};
468	let block_handler = BlockHandler {
469		chain: Arc::downgrade(&chain),
470	};
471	let header_handler = HeaderHandler {
472		chain: Arc::downgrade(&chain),
473	};
474	let chain_tip_handler = ChainHandler {
475		chain: Arc::downgrade(&chain),
476	};
477	let chain_compact_handler = ChainCompactHandler {
478		chain: Arc::downgrade(&chain),
479	};
480	let chain_validation_handler = ChainValidationHandler {
481		chain: Arc::downgrade(&chain),
482	};
483	let status_handler = StatusHandler {
484		chain: Arc::downgrade(&chain),
485		peers: Arc::downgrade(&peers),
486		sync_state: Arc::downgrade(&sync_state),
487		allow_to_stop,
488	};
489	let txhashset_handler = TxHashSetHandler {
490		chain: Arc::downgrade(&chain),
491	};
492	let pool_info_handler = PoolInfoHandler {
493		tx_pool: Arc::downgrade(&tx_pool),
494	};
495	let pool_push_handler = PoolPushHandler {
496		tx_pool: Arc::downgrade(&tx_pool),
497	};
498	let peers_all_handler = PeersAllHandler {
499		peers: Arc::downgrade(&peers),
500	};
501	let peers_connected_handler = PeersConnectedHandler {
502		peers: Arc::downgrade(&peers),
503	};
504	let peer_handler = PeerHandler {
505		peers: Arc::downgrade(&peers),
506	};
507	let version_handler = VersionHandler {
508		chain: Arc::downgrade(&chain),
509	};
510
511	let mut router = Router::new();
512
513	router.add_route("/v1/", Arc::new(index_handler))?;
514	router.add_route("/v1/blocks/*", Arc::new(block_handler))?;
515	router.add_route("/v1/headers/*", Arc::new(header_handler))?;
516	router.add_route("/v1/chain", Arc::new(chain_tip_handler))?;
517	router.add_route("/v1/chain/outputs/*", Arc::new(output_handler))?;
518	router.add_route("/v1/chain/kernels/*", Arc::new(kernel_handler))?;
519	router.add_route("/v1/chain/compact", Arc::new(chain_compact_handler))?;
520	router.add_route("/v1/chain/validate", Arc::new(chain_validation_handler))?;
521	router.add_route("/v1/txhashset/*", Arc::new(txhashset_handler))?;
522	router.add_route("/v1/status", Arc::new(status_handler))?;
523	router.add_route("/v1/pool", Arc::new(pool_info_handler))?;
524	router.add_route("/v1/pool/push_tx", Arc::new(pool_push_handler))?;
525	router.add_route("/v1/peers/all", Arc::new(peers_all_handler))?;
526	router.add_route("/v1/peers/connected", Arc::new(peers_connected_handler))?;
527	router.add_route("/v1/peers/**", Arc::new(peer_handler))?;
528	router.add_route("/v1/version", Arc::new(version_handler))?;
529	Ok(router)
530}