rpc.rs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::collections::HashSet;
  19. use async_trait::async_trait;
  20. use darkfi::{
  21. event_graph::{util::recreate_from_replayer_log, Event},
  22. net::P2pPtr,
  23. rpc::{
  24. jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
  25. p2p_method::HandlerP2p,
  26. server::RequestHandler,
  27. util::{json_map, JsonValue},
  28. },
  29. system::StoppableTaskPtr,
  30. };
  31. use darkfi_serial::deserialize_async_partial;
  32. use smol::lock::MutexGuard;
  33. use tracing::debug;
  34. use super::DarkIrc;
  35. use crate::Privmsg;
  36. #[async_trait]
  37. impl RequestHandler<()> for DarkIrc {
  38. async fn handle_request(&self, req: JsonRequest) -> JsonResult {
  39. debug!(target: "darkirc::rpc", "--> {}", req.stringify().unwrap());
  40. match req.method.as_str() {
  41. "ping" => self.pong(req.id, req.params).await,
  42. "dnet.switch" => self.dnet_switch(req.id, req.params).await,
  43. "dnet.subscribe_events" => self.dnet_subscribe_events(req.id, req.params).await,
  44. "p2p.get_info" => self.p2p_get_info(req.id, req.params).await,
  45. "deg.switch" => self.deg_switch(req.id, req.params).await,
  46. "deg.subscribe_events" => self.deg_subscribe_events(req.id, req.params).await,
  47. "eventgraph.get_info" => self.eg_get_info(req.id, req.params).await,
  48. "eventgraph.replay" => self.eg_rep_info(req.id, req.params).await,
  49. "gource.subscribe_events" => self.gource_subscribe_events(req.id, req.params).await,
  50. _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
  51. }
  52. }
  53. async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
  54. self.rpc_connections.lock().await
  55. }
  56. }
  57. impl DarkIrc {
  58. // RPCAPI:
  59. // Activate or deactivate dnet in the P2P stack.
  60. // By sending `true`, dnet will be activated, and by sending `false` dnet
  61. // will be deactivated. Returns `true` on success.
  62. //
  63. // --> {"jsonrpc": "2.0", "method": "dnet.switch", "params": [true], "id": 42}
  64. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  65. async fn dnet_switch(&self, id: i64, params: JsonValue) -> JsonResult {
  66. let Some(params) = params.get::<Vec<JsonValue>>() else {
  67. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  68. };
  69. if params.len() != 1 || !params[0].is_bool() {
  70. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  71. }
  72. let switch = params[0].get::<bool>().unwrap();
  73. if *switch {
  74. self.p2p.dnet_enable();
  75. } else {
  76. self.p2p.dnet_disable();
  77. }
  78. JsonResponse::new(JsonValue::Boolean(true), id).into()
  79. }
  80. // RPCAPI:
  81. // Initializes a subscription to p2p dnet events.
  82. // Once a subscription is established, `darkirc` will send JSON-RPC notifications of
  83. // new network events to the subscriber.
  84. //
  85. // --> {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [], "id": 1}
  86. // <-- {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [`event`]}
  87. pub async fn dnet_subscribe_events(&self, id: i64, params: JsonValue) -> JsonResult {
  88. let Some(params) = params.get::<Vec<JsonValue>>() else {
  89. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  90. };
  91. if !params.is_empty() {
  92. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  93. }
  94. self.dnet_sub.clone().into()
  95. }
  96. // RPCAPI:
  97. // Initializes a subscription to deg events.
  98. // Once a subscription is established, apps using eventgraph will send JSON-RPC notifications of
  99. // new eventgraph events to the subscriber.
  100. //
  101. // --> {"jsonrpc": "2.0", "method": "deg.subscribe_events", "params": [], "id": 1}
  102. // <-- {"jsonrpc": "2.0", "method": "deg.subscribe_events", "params": [`event`]}
  103. pub async fn deg_subscribe_events(&self, id: i64, params: JsonValue) -> JsonResult {
  104. let Some(params) = params.get::<Vec<JsonValue>>() else {
  105. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  106. };
  107. if !params.is_empty() {
  108. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  109. }
  110. self.deg_sub.clone().into()
  111. }
  112. // RPCAPI:
  113. // Initializes a subscription to the Gource visualization feed.
  114. // Once a subscription is established, every rotating-DAG event
  115. // that successfully decodes as a Privmsg is projected to a
  116. // Gource-shaped record and forwarded to the subscriber.
  117. //
  118. // To feed Gource directly, reformat to the pipe-delimited custom
  119. // log format and pipe it in:
  120. // ```
  121. // ... | jq -r '.params[0]
  122. // | "\(.timestamp)|\(.user)|\(.action)|\(.path)"' \
  123. // | gource --log-format custom -
  124. // ```
  125. //
  126. // --> {"jsonrpc": "2.0", "method": "gource.subscribe_events", "params": [], "id": 1}
  127. // <-- {"jsonrpc": "2.0", "method": "gource.subscribe_events", "params": [`event`]}
  128. pub async fn gource_subscribe_events(&self, id: i64, params: JsonValue) -> JsonResult {
  129. let Some(params) = params.get::<Vec<JsonValue>>() else {
  130. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  131. };
  132. if !params.is_empty() {
  133. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  134. }
  135. self.gource_sub.clone().into()
  136. }
  137. // RPCAPI:
  138. // Activate or deactivate deg in the EVENTGRAPH.
  139. // By sending `true`, deg will be activated, and by sending `false` deg
  140. // will be deactivated. Returns `true` on success.
  141. //
  142. // --> {"jsonrpc": "2.0", "method": "deg.switch", "params": [true], "id": 42}
  143. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  144. async fn deg_switch(&self, id: i64, params: JsonValue) -> JsonResult {
  145. let Some(params) = params.get::<Vec<JsonValue>>() else {
  146. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  147. };
  148. if params.len() != 1 || !params[0].is_bool() {
  149. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  150. }
  151. let switch = params[0].get::<bool>().unwrap();
  152. if *switch {
  153. self.event_graph.deg_enable();
  154. } else {
  155. self.event_graph.deg_disable();
  156. }
  157. JsonResponse::new(JsonValue::Boolean(true), id).into()
  158. }
  159. // RPCAPI:
  160. // Get EVENTGRAPH info.
  161. //
  162. // --> {"jsonrpc": "2.0", "method": "deg.switch", "params": [true], "id": 42}
  163. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  164. async fn eg_get_info(&self, id: i64, params: JsonValue) -> JsonResult {
  165. let Some(params_) = params.get::<Vec<JsonValue>>() else {
  166. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  167. };
  168. if !params_.is_empty() {
  169. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  170. }
  171. self.event_graph.eventgraph_info(id, params).await
  172. }
  173. // RPCAPI:
  174. // Get replayed EVENTGRAPH info.
  175. //
  176. // --> {"jsonrpc": "2.0", "method": "eventgraph.replay", "params": ..., "id": 42}
  177. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  178. async fn eg_rep_info(&self, id: i64, params: JsonValue) -> JsonResult {
  179. let Some(params_) = params.get::<Vec<JsonValue>>() else {
  180. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  181. };
  182. if !params_.is_empty() {
  183. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  184. }
  185. recreate_from_replayer_log(&self.replay_datastore).await
  186. }
  187. }
  188. impl HandlerP2p for DarkIrc {
  189. fn p2p(&self) -> P2pPtr {
  190. self.p2p.clone()
  191. }
  192. }
  193. /// Project a single rotating-DAG event to a Gource-shaped record.
  194. ///
  195. /// Returns `None` if the event content isn't a [`Privmsg`] or the
  196. /// privmsg's channel field is empty (in which case there's nothing
  197. /// useful to visualize).
  198. pub async fn privmsg_event_to_gource(event: &Event) -> Option<JsonValue> {
  199. let privmsg: Privmsg = match deserialize_async_partial(event.content()).await {
  200. Ok((v, _)) => v,
  201. Err(_) => return None,
  202. };
  203. if privmsg.channel.is_empty() {
  204. return None
  205. }
  206. let path = if let Some(name) = privmsg.channel.strip_prefix('#') {
  207. format!("channels/{name}")
  208. } else {
  209. format!("dms/{}", privmsg.channel)
  210. };
  211. // Gource's custom log expects Unix seconds, not millis.
  212. let unix_secs = event.header.timestamp / 1_000;
  213. Some(json_map([
  214. ("timestamp", JsonValue::String(unix_secs.to_string())),
  215. ("user", JsonValue::String(privmsg.nick.clone())),
  216. // "M" = modify. We always emit "M" because tracking
  217. // first-touch (which would justify "A") would need
  218. // cross-event state and gource creates the file on first
  219. // reference automatically anyway.
  220. ("action", JsonValue::String("M".into())),
  221. ("path", JsonValue::String(path)),
  222. ]))
  223. }