rpc.rs 28 KB

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