rpc.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  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::HashMap;
  19. use darkfi::{
  20. blockchain::{header_store::PowData, BlockInfo},
  21. rpc::jsonrpc::{
  22. ErrorCode::{InternalError, InvalidParams},
  23. JsonError, JsonResponse, JsonResult,
  24. },
  25. tx::Transaction,
  26. util::{encoding::base64, parse::encode_base10},
  27. };
  28. use darkfi_serial::serialize_async;
  29. use monero::{consensus::encode::Encodable, VarInt};
  30. use tiny_keccak::{Hasher, Keccak};
  31. use tinyjson::JsonValue;
  32. use crate::{DifficultyIndex, Explorer};
  33. struct ContractCallInfo {
  34. contract_id: String,
  35. contract_tag: Option<String>,
  36. func: String,
  37. size: u64,
  38. }
  39. impl ContractCallInfo {
  40. fn new(contract_id: String, func: String, size: u64) -> Self {
  41. let contract_tag = match contract_id.as_str() {
  42. "BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o" => Some("Money".to_string()),
  43. "Fd8kfCuqU8BoFFp6GcXv5pC8XXRkBK7gUPQX5XDz7iXj" => Some("DAO".to_string()),
  44. "EJs7oEjKkvCeEVCmpRsd6fEoTGCFJ7WKUBfmAjwaegN" => Some("Deployoor".to_string()),
  45. _ => None,
  46. };
  47. Self { contract_id, contract_tag, func, size }
  48. }
  49. fn to_json(&self) -> JsonValue {
  50. let tag = match &self.contract_tag {
  51. Some(t) => JsonValue::String(t.clone()),
  52. None => JsonValue::Null,
  53. };
  54. JsonValue::Object(HashMap::from([
  55. ("contract_id".to_string(), JsonValue::String(self.contract_id.clone())),
  56. ("contract_tag".to_string(), tag),
  57. ("func".to_string(), JsonValue::String(self.func.clone())),
  58. ("size".to_string(), JsonValue::Number(self.size as f64)),
  59. ]))
  60. }
  61. }
  62. struct TransactionInfo {
  63. hash: String,
  64. calls: Vec<ContractCallInfo>,
  65. fee: u64,
  66. size: u64,
  67. }
  68. impl TransactionInfo {
  69. async fn new(tx: &Transaction) -> Self {
  70. let mut fee = 0;
  71. let mut calls = Vec::with_capacity(tx.calls.len());
  72. for call in &tx.calls {
  73. let func = call.data.data.first().copied();
  74. if let Ok(parsed_fee) = call.data.money_fee_value() {
  75. fee = parsed_fee;
  76. }
  77. calls.push(ContractCallInfo::new(
  78. call.data.contract_id.to_string(),
  79. func.map(|func| format!("0x{func:02x}")).unwrap_or_else(|| "empty".to_string()),
  80. call.data.data.len() as u64,
  81. ));
  82. }
  83. Self {
  84. hash: tx.hash().to_string(),
  85. calls,
  86. fee,
  87. size: serialize_async(tx).await.len() as u64,
  88. }
  89. }
  90. fn to_json(&self) -> JsonValue {
  91. let calls = self.calls.iter().map(|c| c.to_json()).collect();
  92. JsonValue::Object(HashMap::from([
  93. ("hash".to_string(), JsonValue::String(self.hash.clone())),
  94. ("calls".to_string(), JsonValue::Array(calls)),
  95. ("fee".to_string(), JsonValue::String(encode_base10(self.fee, 8))),
  96. ("size".to_string(), JsonValue::Number(self.size as f64)),
  97. ]))
  98. }
  99. }
  100. /// Full transaction info for the get_tx RPC endpoint
  101. struct ExplTxInfo {
  102. hash: String,
  103. from_block: u64,
  104. confirmations: u64,
  105. fee: u64,
  106. size: u64,
  107. n_calls: u64,
  108. calls: Vec<ContractCallInfo>,
  109. raw: String,
  110. }
  111. impl ExplTxInfo {
  112. async fn new(tx: &Transaction, block_height: u64, current_height: u64) -> Self {
  113. let mut fee = 0;
  114. let mut calls = Vec::with_capacity(tx.calls.len());
  115. for call in &tx.calls {
  116. let func = call.data.data.first().copied();
  117. if let Ok(parsed_fee) = call.data.money_fee_value() {
  118. fee = parsed_fee;
  119. }
  120. calls.push(ContractCallInfo::new(
  121. call.data.contract_id.to_string(),
  122. func.map(|func| format!("0x{func:02x}")).unwrap_or_else(|| "empty".to_string()),
  123. call.data.data.len() as u64,
  124. ));
  125. }
  126. let raw_bytes = serialize_async(tx).await;
  127. let confirmations =
  128. if current_height >= block_height { current_height - block_height + 1 } else { 0 };
  129. Self {
  130. hash: tx.hash().to_string(),
  131. from_block: block_height,
  132. confirmations,
  133. fee,
  134. size: raw_bytes.len() as u64,
  135. n_calls: tx.calls.len() as u64,
  136. calls,
  137. raw: base64::encode(&raw_bytes),
  138. }
  139. }
  140. fn to_json(&self) -> JsonValue {
  141. JsonValue::Object(HashMap::from([
  142. ("hash".to_string(), JsonValue::String(self.hash.clone())),
  143. ("from_block".to_string(), JsonValue::Number(self.from_block as f64)),
  144. ("confirmations".to_string(), JsonValue::Number(self.confirmations as f64)),
  145. ("fee".to_string(), JsonValue::String(encode_base10(self.fee, 8))),
  146. ("size".to_string(), JsonValue::Number(self.size as f64)),
  147. ("n_calls".to_string(), JsonValue::Number(self.n_calls as f64)),
  148. (
  149. "calls".to_string(),
  150. JsonValue::Array(self.calls.iter().map(|c| c.to_json()).collect()),
  151. ),
  152. ("raw".to_string(), JsonValue::String(self.raw.clone())),
  153. ]))
  154. }
  155. }
  156. struct ExplBlockInfo {
  157. height: u64,
  158. hash: String,
  159. version: u8,
  160. previous_hash: String,
  161. nonce: u64,
  162. timestamp: u64,
  163. transactions_root: String,
  164. state_root: String,
  165. size: u64,
  166. difficulty: u64,
  167. cumulative: u64,
  168. powtype: String,
  169. monero_hash: Option<String>,
  170. txs: Vec<TransactionInfo>,
  171. coinbase: CoinbaseInfo,
  172. }
  173. impl ExplBlockInfo {
  174. async fn new(block: &BlockInfo, diff: &DifficultyIndex) -> Self {
  175. let mut monero_hash = None;
  176. let powtype = match &block.header.pow_data {
  177. PowData::DarkFi => "DarkFi".to_string(),
  178. PowData::Monero(powdata) => {
  179. // Calculate the Monero block header hash
  180. let mut blockhashing_blob = powdata.to_block_hashing_blob();
  181. // Monero prefixes a VarInt of the blob len before getting the
  182. // block hash but doesn't do this when getting the PoW hash :)
  183. let mut header = vec![];
  184. VarInt(blockhashing_blob.len() as u64).consensus_encode(&mut header).unwrap();
  185. header.append(&mut blockhashing_blob);
  186. let mut keccak = Keccak::v256();
  187. keccak.update(&header);
  188. let mut hash = [0u8; 32];
  189. keccak.finalize(&mut hash);
  190. monero_hash = Some(hex::encode(hash));
  191. "Monero".to_string()
  192. }
  193. };
  194. let mut txs = Vec::with_capacity(block.txs.len());
  195. for tx in &block.txs {
  196. txs.push(TransactionInfo::new(tx).await);
  197. }
  198. let coinbase = CoinbaseInfo::new(&block.txs[block.txs.len() - 1]).await;
  199. Self {
  200. height: block.header.height as u64,
  201. hash: block.header.hash().to_string(),
  202. version: block.header.version,
  203. previous_hash: block.header.previous.to_string(),
  204. nonce: block.header.nonce as u64,
  205. timestamp: block.header.timestamp.inner(),
  206. transactions_root: block.header.transactions_root.to_string(),
  207. state_root: hex::encode(block.header.state_root),
  208. size: serialize_async(block).await.len() as u64,
  209. difficulty: diff.difficulty,
  210. cumulative: diff.cumulative,
  211. powtype,
  212. monero_hash,
  213. txs,
  214. coinbase,
  215. }
  216. }
  217. fn to_json(&self) -> JsonValue {
  218. let monero_hash = if let Some(hash) = &self.monero_hash {
  219. JsonValue::String(hash.to_string())
  220. } else {
  221. JsonValue::Null
  222. };
  223. JsonValue::Object(HashMap::from([
  224. ("height".to_string(), JsonValue::Number(self.height as f64)),
  225. ("hash".to_string(), JsonValue::String(self.hash.clone())),
  226. ("version".to_string(), JsonValue::Number(self.version as f64)),
  227. ("previous_hash".to_string(), JsonValue::String(self.previous_hash.clone())),
  228. ("nonce".to_string(), JsonValue::Number(self.nonce as f64)),
  229. ("timestamp".to_string(), JsonValue::Number(self.timestamp as f64)),
  230. ("transactions_root".to_string(), JsonValue::String(self.transactions_root.clone())),
  231. ("state_root".to_string(), JsonValue::String(self.state_root.clone())),
  232. ("size".to_string(), JsonValue::Number(self.size as f64)),
  233. ("difficulty".to_string(), JsonValue::Number(self.difficulty as f64)),
  234. ("cumulative".to_string(), JsonValue::Number(self.cumulative as f64)),
  235. ("powtype".to_string(), JsonValue::String(self.powtype.clone())),
  236. ("monero_hash".to_string(), monero_hash),
  237. ("txs".to_string(), JsonValue::Array(self.txs.iter().map(|t| t.to_json()).collect())),
  238. ("coinbase".to_string(), self.coinbase.to_json()),
  239. ]))
  240. }
  241. }
  242. struct CoinbaseInfo {
  243. hash: String,
  244. reward: u64,
  245. size: u64,
  246. }
  247. impl CoinbaseInfo {
  248. async fn new(tx: &Transaction) -> Self {
  249. Self {
  250. hash: tx.hash().to_string(),
  251. reward: 0,
  252. size: serialize_async(tx).await.len() as u64,
  253. }
  254. }
  255. fn to_json(&self) -> JsonValue {
  256. JsonValue::Object(HashMap::from([
  257. ("hash".to_string(), JsonValue::String(self.hash.clone())),
  258. ("reward".to_string(), JsonValue::Number(self.reward as f64)),
  259. ("size".to_string(), JsonValue::Number(self.size as f64)),
  260. ]))
  261. }
  262. }
  263. impl Explorer {
  264. pub async fn rpc_current_difficulty(&self, id: i64, _params: JsonValue) -> JsonResult {
  265. // Get latest height
  266. let Ok(Some(height)) = self.get_height() else {
  267. return JsonError::new(InternalError, None, id).into()
  268. };
  269. let Ok(Some(difficulty)) = self.get_difficulty(height) else {
  270. return JsonError::new(InternalError, None, id).into()
  271. };
  272. JsonResponse::new(
  273. JsonValue::Array(vec![
  274. (difficulty.difficulty as f64).into(),
  275. (difficulty.cumulative as f64).into(),
  276. ]),
  277. id,
  278. )
  279. .into()
  280. }
  281. pub async fn rpc_current_height(&self, id: i64, _params: JsonValue) -> JsonResult {
  282. let Ok(Some(height)) = self.get_height() else {
  283. return JsonError::new(InternalError, None, id).into()
  284. };
  285. JsonResponse::new(JsonValue::Number(height as f64), id).into()
  286. }
  287. pub async fn rpc_latest_blocks(&self, id: i64, params: JsonValue) -> JsonResult {
  288. let Some(params) = params.get::<Vec<JsonValue>>() else {
  289. return JsonError::new(InvalidParams, None, id).into()
  290. };
  291. if params.len() != 1 || !params[0].is_number() {
  292. return JsonError::new(InvalidParams, None, id).into()
  293. }
  294. let n_blocks = *params[0].get::<f64>().unwrap() as u64;
  295. let Ok(Some(height)) = self.get_height() else {
  296. return JsonError::new(InternalError, None, id).into()
  297. };
  298. // Calculate how many blocks we can actually return
  299. let start_height = height.saturating_sub(n_blocks.saturating_sub(1));
  300. let mut blocks = Vec::with_capacity((height - start_height + 1) as usize);
  301. for h in (start_height..=height).rev() {
  302. let Ok(Some((header, tx_count, size))) = self.get_block_summary(h).await else {
  303. return JsonError::new(InternalError, None, id).into()
  304. };
  305. let powtype = match header.pow_data {
  306. PowData::DarkFi => "DarkFi".to_string(),
  307. PowData::Monero(_) => "Monero".to_string(),
  308. };
  309. blocks.push(JsonValue::Object(HashMap::from([
  310. ("height".to_string(), JsonValue::Number(header.height as f64)),
  311. ("size".to_string(), JsonValue::Number(size as f64)),
  312. ("n_txs".to_string(), JsonValue::Number(tx_count as f64)),
  313. ("timestamp".to_string(), JsonValue::Number(header.timestamp.inner() as f64)),
  314. ("powtype".to_string(), JsonValue::String(powtype)),
  315. ("hash".to_string(), JsonValue::String(header.hash().to_string())),
  316. ])));
  317. }
  318. JsonResponse::new(JsonValue::Array(blocks), id).into()
  319. }
  320. pub async fn rpc_get_block(&self, id: i64, params: JsonValue) -> JsonResult {
  321. let Some(params) = params.get::<Vec<JsonValue>>() else {
  322. return JsonError::new(InvalidParams, None, id).into()
  323. };
  324. if params.len() != 1 {
  325. return JsonError::new(InvalidParams, None, id).into()
  326. }
  327. let height = if params[0].is_string() {
  328. let Ok(hash) = hex::decode(params[0].get::<String>().unwrap()) else {
  329. return JsonError::new(InvalidParams, None, id).into()
  330. };
  331. let Ok(Some(height_bytes)) = self.header_indices.get(&hash) else {
  332. return JsonError::new(InternalError, None, id).into()
  333. };
  334. let height_bytes = height_bytes.to_vec();
  335. u64::from_le_bytes(height_bytes.try_into().unwrap())
  336. } else if params[0].is_number() {
  337. *params[0].get::<f64>().unwrap() as u64
  338. } else {
  339. return JsonError::new(InvalidParams, None, id).into()
  340. };
  341. let Ok(Some(block)) = self.get_block(height).await else {
  342. return JsonError::new(InternalError, None, id).into()
  343. };
  344. let Ok(Some(diff)) = self.get_difficulty(height) else {
  345. return JsonError::new(InternalError, None, id).into()
  346. };
  347. let info = ExplBlockInfo::new(&block, &diff).await;
  348. JsonResponse::new(info.to_json(), id).into()
  349. }
  350. pub async fn rpc_get_tx(&self, id: i64, params: JsonValue) -> JsonResult {
  351. let Some(params) = params.get::<Vec<JsonValue>>() else {
  352. return JsonError::new(InvalidParams, None, id).into()
  353. };
  354. if params.len() != 1 || !params[0].is_string() {
  355. return JsonError::new(InvalidParams, None, id).into()
  356. }
  357. let tx_hash_str = params[0].get::<String>().unwrap();
  358. // Get current height for confirmations calculation
  359. let Ok(Some(current_height)) = self.get_height() else {
  360. return JsonError::new(InternalError, None, id).into()
  361. };
  362. // Get transaction by hash
  363. let Ok(Some((tx, block_height))) = self.get_tx_by_hash_str(tx_hash_str).await else {
  364. return JsonError::new(InternalError, None, id).into()
  365. };
  366. let info = ExplTxInfo::new(&tx, block_height, current_height).await;
  367. JsonResponse::new(info.to_json(), id).into()
  368. }
  369. /// Search for a block or transaction by hash.
  370. /// Returns `{"type": "block", "height": N}` or `{"type": "tx"}` depending on what was found.
  371. pub async fn rpc_search(&self, id: i64, params: JsonValue) -> JsonResult {
  372. let Some(params) = params.get::<Vec<JsonValue>>() else {
  373. return JsonError::new(InvalidParams, None, id).into()
  374. };
  375. if params.len() != 1 || !params[0].is_string() {
  376. return JsonError::new(InvalidParams, None, id).into()
  377. }
  378. let query = params[0].get::<String>().unwrap();
  379. // Try to decode as hex
  380. let Ok(hash_bytes) = hex::decode(query) else {
  381. return JsonError::new(InvalidParams, None, id).into()
  382. };
  383. // Try block hash first (serialized blake3 hash)
  384. if let Ok(Some(height_bytes)) = self.header_indices.get(&hash_bytes) {
  385. let height = u64::from_le_bytes(height_bytes.try_into().unwrap_or([0u8; 8]));
  386. return JsonResponse::new(
  387. JsonValue::Object(HashMap::from([
  388. ("type".to_string(), JsonValue::String("block".to_string())),
  389. ("height".to_string(), JsonValue::Number(height as f64)),
  390. ])),
  391. id,
  392. )
  393. .into()
  394. }
  395. // Try transaction hash (32 bytes)
  396. if hash_bytes.len() == 32 {
  397. let mut tx_hash = [0u8; 32];
  398. tx_hash.copy_from_slice(&hash_bytes);
  399. if self.tx_indices.get(&tx_hash).ok().flatten().is_some() {
  400. return JsonResponse::new(
  401. JsonValue::Object(HashMap::from([(
  402. "type".to_string(),
  403. JsonValue::String("tx".to_string()),
  404. )])),
  405. id,
  406. )
  407. .into()
  408. }
  409. }
  410. // Not found
  411. JsonError::new(InternalError, Some("Not found".to_string()), id).into()
  412. }
  413. /// Calculate the current network hashrate.
  414. /// Hashrate = difficulty / average_block_time
  415. /// We use the last N blocks to smooth out variance.
  416. pub async fn rpc_get_hashrate(&self, id: i64, _params: JsonValue) -> JsonResult {
  417. const BLOCKS_TO_AVERAGE: u64 = 30;
  418. let Ok(Some(height)) = self.get_height() else {
  419. return JsonError::new(InternalError, None, id).into()
  420. };
  421. if height < 2 {
  422. return JsonResponse::new(JsonValue::Number(0.0), id).into()
  423. }
  424. let start_height = height.saturating_sub(BLOCKS_TO_AVERAGE);
  425. // Get timestamps from start and end blocks
  426. let Ok(Some(start_header)) = self.get_header(start_height).await else {
  427. return JsonError::new(InternalError, None, id).into()
  428. };
  429. let Ok(Some(end_header)) = self.get_header(height).await else {
  430. return JsonError::new(InternalError, None, id).into()
  431. };
  432. let time_diff = end_header.timestamp.inner() as f64 - start_header.timestamp.inner() as f64;
  433. let blocks_mined = (height - start_height) as f64;
  434. if time_diff <= 0.0 || blocks_mined <= 0.0 {
  435. return JsonResponse::new(JsonValue::Number(0.0), id).into()
  436. }
  437. // Get current difficulty
  438. let Ok(Some(diff)) = self.get_difficulty(height) else {
  439. return JsonError::new(InternalError, None, id).into()
  440. };
  441. // Average block time in seconds
  442. let avg_block_time = time_diff / blocks_mined;
  443. // Hashrate = difficulty / block_time
  444. // This approximates hashes per second needed to find a block at current difficulty
  445. let hashrate = (diff.difficulty as f64) / avg_block_time;
  446. JsonResponse::new(JsonValue::Number(hashrate), id).into()
  447. }
  448. /// Get contract information by ID.
  449. /// Params: `[contract_id: String]`
  450. /// Returns: `{ contract_id, locked, wasm_size, deploy_block, deploy_tx }`
  451. pub async fn rpc_get_contract(&self, id: i64, params: JsonValue) -> JsonResult {
  452. let Some(params) = params.get::<Vec<JsonValue>>() else {
  453. return JsonError::new(InvalidParams, None, id).into()
  454. };
  455. if params.len() != 1 || !params[0].is_string() {
  456. return JsonError::new(InvalidParams, None, id).into()
  457. }
  458. let contract_id_str = params[0].get::<String>().unwrap();
  459. let Ok(Some(contract)) = self.get_contract(contract_id_str).await else {
  460. return JsonError::new(InternalError, Some("Contract not found".to_string()), id).into()
  461. };
  462. JsonResponse::new(
  463. JsonValue::Object(HashMap::from([
  464. ("contract_id".to_string(), JsonValue::String(contract.contract_id.to_string())),
  465. ("locked".to_string(), JsonValue::Boolean(contract.locked)),
  466. ("wasm_size".to_string(), JsonValue::Number(contract.wasm_size as f64)),
  467. ("deploy_block".to_string(), JsonValue::Number(contract.deploy_block as f64)),
  468. ("deploy_tx".to_string(), JsonValue::String(hex::encode(contract.deploy_tx_hash))),
  469. ])),
  470. id,
  471. )
  472. .into()
  473. }
  474. /// List all contracts.
  475. /// Params: `[locked_filter: bool | null] (optional)`
  476. /// Returns: Array of contract objects
  477. pub async fn rpc_list_contracts(&self, id: i64, params: JsonValue) -> JsonResult {
  478. let locked_filter = if let Some(params) = params.get::<Vec<JsonValue>>() {
  479. if !params.is_empty() {
  480. params[0].get::<bool>().copied()
  481. } else {
  482. None
  483. }
  484. } else {
  485. None
  486. };
  487. let Ok(contracts) = self.list_contracts(locked_filter).await else {
  488. return JsonError::new(InternalError, None, id).into()
  489. };
  490. let contracts_json: Vec<JsonValue> = contracts
  491. .iter()
  492. .map(|c| {
  493. JsonValue::Object(HashMap::from([
  494. ("contract_id".to_string(), JsonValue::String(c.contract_id.to_string())),
  495. ("locked".to_string(), JsonValue::Boolean(c.locked)),
  496. ("wasm_size".to_string(), JsonValue::Number(c.wasm_size as f64)),
  497. ("deploy_block".to_string(), JsonValue::Number(c.deploy_block as f64)),
  498. ("deploy_tx".to_string(), JsonValue::String(hex::encode(c.deploy_tx_hash))),
  499. ]))
  500. })
  501. .collect();
  502. JsonResponse::new(JsonValue::Array(contracts_json), id).into()
  503. }
  504. /// Get contract count.
  505. /// Returns: Number of contracts
  506. pub async fn rpc_contract_count(&self, id: i64, _params: JsonValue) -> JsonResult {
  507. let Ok(count) = self.get_contract_count() else {
  508. return JsonError::new(InternalError, None, id).into()
  509. };
  510. JsonResponse::new(JsonValue::Number(count as f64), id).into()
  511. }
  512. /// Get blockchain statistics from stored data.
  513. /// Returns daily stats, monthly growth, and tx per block stats.
  514. pub async fn rpc_get_stats(&self, id: i64, _params: JsonValue) -> JsonResult {
  515. // Get daily stats from kvdb
  516. let daily_stats = match self.get_all_daily_stats().await {
  517. Ok(stats) => stats,
  518. Err(_) => return JsonError::new(InternalError, None, id).into(),
  519. };
  520. // Get monthly stats from kvdb
  521. let monthly_stats = match self.get_all_monthly_stats().await {
  522. Ok(stats) => stats,
  523. Err(_) => return JsonError::new(InternalError, None, id).into(),
  524. };
  525. // Convert daily stats to JSON (for graph)
  526. let daily_json: Vec<JsonValue> = daily_stats
  527. .iter()
  528. .map(|(day, stats)| {
  529. let avg_tx = if stats.block_count > 0 {
  530. stats.user_tx_count as f64 / stats.block_count as f64
  531. } else {
  532. 0.0
  533. };
  534. JsonValue::Object(HashMap::from([
  535. ("day".to_string(), JsonValue::Number(*day as f64)),
  536. ("avg_tx".to_string(), JsonValue::Number(avg_tx)),
  537. ("block_count".to_string(), JsonValue::Number(stats.block_count as f64)),
  538. ("user_tx_count".to_string(), JsonValue::Number(stats.user_tx_count as f64)),
  539. ("total_size".to_string(), JsonValue::Number(stats.total_size as f64)),
  540. ]))
  541. })
  542. .collect();
  543. // Convert monthly stats to JSON with cumulative
  544. let mut cumulative: u64 = 0;
  545. let monthly_json: Vec<JsonValue> = monthly_stats
  546. .iter()
  547. .map(|(year, month, stats)| {
  548. cumulative += stats.total_size;
  549. JsonValue::Object(HashMap::from([
  550. ("year".to_string(), JsonValue::Number(*year as f64)),
  551. ("month".to_string(), JsonValue::Number(*month as f64)),
  552. (
  553. "size_mb".to_string(),
  554. JsonValue::Number(stats.total_size as f64 / 1_048_576.0),
  555. ),
  556. (
  557. "cumulative_mb".to_string(),
  558. JsonValue::Number(cumulative as f64 / 1_048_576.0),
  559. ),
  560. ("block_count".to_string(), JsonValue::Number(stats.block_count as f64)),
  561. ]))
  562. })
  563. .collect();
  564. // Calculate tx per block stats for time periods
  565. // Get current day
  566. let current_day = if let Some((day, _)) = daily_stats.last() { *day } else { 0 };
  567. fn calc_period_stats(
  568. daily_stats: &[(u64, crate::db::DailyStats)],
  569. from_day: u64,
  570. to_day: u64,
  571. ) -> (f64, f64, u64, u64) {
  572. let mut total_blocks: u64 = 0;
  573. let mut total_user_tx: u64 = 0;
  574. let mut empty_blocks: u64 = 0;
  575. for (day, stats) in daily_stats {
  576. if *day >= from_day && *day <= to_day {
  577. total_blocks += stats.block_count;
  578. total_user_tx += stats.user_tx_count;
  579. // A block is "empty" if it has 0 user transactions
  580. // We approximate empty blocks as: blocks where avg user_tx < 1
  581. // But we don't have per-block data, so we estimate
  582. if stats.block_count > 0 && stats.user_tx_count == 0 {
  583. empty_blocks += stats.block_count;
  584. }
  585. }
  586. }
  587. let avg_tx =
  588. if total_blocks > 0 { total_user_tx as f64 / total_blocks as f64 } else { 0.0 };
  589. let empty_pct = if total_blocks > 0 {
  590. empty_blocks as f64 / total_blocks as f64 * 100.0
  591. } else {
  592. 0.0
  593. };
  594. (avg_tx, empty_pct, total_user_tx, total_blocks)
  595. }
  596. let (avg_day, empty_day, total_day, blocks_day) =
  597. calc_period_stats(&daily_stats, current_day, current_day);
  598. let (avg_week, empty_week, total_week, blocks_week) =
  599. calc_period_stats(&daily_stats, current_day.saturating_sub(6), current_day);
  600. let (avg_month, empty_month, total_month, blocks_month) =
  601. calc_period_stats(&daily_stats, current_day.saturating_sub(29), current_day);
  602. let (avg_year, empty_year, total_year, blocks_year) =
  603. calc_period_stats(&daily_stats, current_day.saturating_sub(364), current_day);
  604. fn stats_to_json(avg: f64, empty_pct: f64, total: u64, block_count: u64) -> JsonValue {
  605. JsonValue::Object(HashMap::from([
  606. ("avg_tx".to_string(), JsonValue::Number(avg)),
  607. ("empty_pct".to_string(), JsonValue::Number(empty_pct)),
  608. ("total_tx".to_string(), JsonValue::Number(total as f64)),
  609. ("block_count".to_string(), JsonValue::Number(block_count as f64)),
  610. ]))
  611. }
  612. let tx_per_block = JsonValue::Object(HashMap::from([
  613. ("last_day".to_string(), stats_to_json(avg_day, empty_day, total_day, blocks_day)),
  614. ("last_week".to_string(), stats_to_json(avg_week, empty_week, total_week, blocks_week)),
  615. (
  616. "last_month".to_string(),
  617. stats_to_json(avg_month, empty_month, total_month, blocks_month),
  618. ),
  619. ("last_year".to_string(), stats_to_json(avg_year, empty_year, total_year, blocks_year)),
  620. ]));
  621. JsonResponse::new(
  622. JsonValue::Object(HashMap::from([
  623. ("daily_stats".to_string(), JsonValue::Array(daily_json)),
  624. ("monthly_growth".to_string(), JsonValue::Array(monthly_json)),
  625. ("tx_per_block".to_string(), tx_per_block),
  626. ])),
  627. id,
  628. )
  629. .into()
  630. }
  631. }