db.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  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::{io, str::FromStr};
  19. use bytemuck::{Pod, Zeroable};
  20. use darkfi::{
  21. blockchain::{BlockInfo, Header},
  22. tx::Transaction,
  23. };
  24. use darkfi_deployooor_contract::{model::LockParamsV1, DeployFunction};
  25. use darkfi_sdk::{
  26. crypto::{schnorr::Signature, ContractId, DEPLOYOOOR_CONTRACT_ID},
  27. deploy::DeployParamsV1,
  28. };
  29. use darkfi_serial::{
  30. async_trait, deserialize, deserialize_async, serialize, serialize_async, SerialDecodable,
  31. SerialEncodable,
  32. };
  33. use sled::{transaction::TransactionError, Transactional};
  34. use tapes::{BlobTape, FixedSizedTape, Persistence, TapeOpenOptions, Tapes};
  35. use tracing::info;
  36. use super::Explorer;
  37. /// Contract information stored in sled
  38. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  39. pub struct ContractData {
  40. pub contract_id: ContractId,
  41. pub locked: bool,
  42. pub wasm_size: u64,
  43. pub deploy_block: u64,
  44. pub deploy_tx_hash: [u8; 32],
  45. }
  46. /// Index entry for a block pointing to block data in the blob tape
  47. #[derive(Debug, Copy, Clone, Pod, Zeroable)]
  48. #[repr(C)]
  49. pub struct BlockIndex {
  50. pub offset: u64,
  51. pub length: u64,
  52. pub tx_count: u64,
  53. pub tx_start_idx: u64,
  54. }
  55. /// Index entry for a transaction pointing to tx data in the blob tape
  56. #[derive(Debug, Copy, Clone, Pod, Zeroable)]
  57. #[repr(C)]
  58. pub struct TxIndex {
  59. pub offset: u64,
  60. pub length: u64,
  61. pub block_height: u64,
  62. }
  63. /// Difficulty data for a block
  64. #[derive(Debug, Copy, Clone, Pod, Zeroable)]
  65. #[repr(C)]
  66. pub struct DifficultyIndex {
  67. pub difficulty: u64,
  68. pub cumulative: u64,
  69. }
  70. /// Structure holding all tapes in the database.
  71. pub struct TapesDatabase {
  72. pub block_index: FixedSizedTape<BlockIndex>,
  73. pub tx_index: FixedSizedTape<TxIndex>,
  74. pub difficulty_index: FixedSizedTape<DifficultyIndex>,
  75. pub blocks: BlobTape,
  76. pub transactions: BlobTape,
  77. }
  78. impl Explorer {
  79. pub fn open_tapes(db: &Tapes, options: &TapeOpenOptions) -> io::Result<TapesDatabase> {
  80. let mut tx = db.append();
  81. let block_index = tx.open_fixed_sized_tape("block_index", options)?;
  82. let tx_index = tx.open_fixed_sized_tape("tx_index", options)?;
  83. let difficulty_index = tx.open_fixed_sized_tape("diff_index", options)?;
  84. let blocks = tx.open_blob_tape("blocks", options)?;
  85. let transactions = tx.open_blob_tape("transactions", options)?;
  86. tx.commit(Persistence::Buffer)?;
  87. Ok(TapesDatabase { block_index, tx_index, difficulty_index, blocks, transactions })
  88. }
  89. /// Append a new block
  90. pub async fn append_block(&self, block: &BlockInfo, diff: &DifficultyIndex) -> io::Result<()> {
  91. let mut tx = self.tapes_db.append();
  92. let block_offset = tx.blob_tape_len(&self.database.blocks).unwrap_or(0);
  93. let tx_blob_offset = tx.blob_tape_len(&self.database.transactions).unwrap_or(0);
  94. let tx_start_idx = tx.fixed_sized_tape_len(&self.database.tx_index).unwrap_or(0);
  95. // Append block header
  96. let header_data = serialize_async(&block.header).await;
  97. tx.append_bytes(&self.database.blocks, &header_data)?;
  98. // Append all block transactions
  99. let mut current_tx_offset = tx_blob_offset;
  100. for transaction in &block.txs {
  101. let tx_data = serialize_async(transaction).await;
  102. tx.append_bytes(&self.database.transactions, &tx_data)?;
  103. let tx_idx = TxIndex {
  104. offset: current_tx_offset,
  105. length: tx_data.len() as u64,
  106. block_height: block.header.height as u64,
  107. };
  108. tx.append_entries(&self.database.tx_index, std::slice::from_ref(&tx_idx))?;
  109. current_tx_offset += tx_data.len() as u64;
  110. }
  111. // Append block index
  112. let block_idx = BlockIndex {
  113. offset: block_offset,
  114. length: header_data.len() as u64,
  115. tx_count: block.txs.len() as u64,
  116. tx_start_idx,
  117. };
  118. tx.append_entries(&self.database.block_index, std::slice::from_ref(&block_idx))?;
  119. // Append difficulty
  120. tx.append_entries(&self.database.difficulty_index, std::slice::from_ref(diff))?;
  121. // Commit Tapes first
  122. tx.commit(Persistence::SyncData)?;
  123. // Prepare data for atomic sled transaction
  124. let header_hash = serialize_async(&block.header.hash()).await;
  125. // Store height as u64 (8 bytes) to match lookup format
  126. let height_bytes = (block.header.height as u64).to_le_bytes();
  127. // Collect tx hashes and their indices
  128. let mut tx_entries: Vec<([u8; 32], [u8; 8])> = Vec::with_capacity(block.txs.len());
  129. for (i, transaction) in block.txs.iter().enumerate() {
  130. let tx_hash = *transaction.hash().inner();
  131. let tx_idx_pos = tx_start_idx + i as u64;
  132. tx_entries.push((tx_hash, tx_idx_pos.to_le_bytes()));
  133. }
  134. // Scan for contract deployments and locks
  135. let (new_contracts, locked_contracts) =
  136. self.scan_contract_calls(block, block.header.height as u64).await;
  137. // Atomic sled transaction for tx_indices, header_indices, and contracts
  138. (&self.tx_indices, &self.header_indices, &self.contracts)
  139. .transaction(|(tx_tree, header_tree, contracts_tree)| {
  140. // Insert all transaction indices
  141. for (hash, idx) in &tx_entries {
  142. tx_tree.insert(hash.as_slice(), idx.as_slice())?;
  143. }
  144. // Insert header hash -> height mapping
  145. header_tree.insert(header_hash.as_slice(), height_bytes.as_slice())?;
  146. // Insert new contracts
  147. for contract in &new_contracts {
  148. contracts_tree
  149. .insert(serialize(&contract.contract_id.inner()), serialize(contract))?;
  150. }
  151. // Update locked contracts
  152. for contract_id in &locked_contracts {
  153. let data = contracts_tree.get(serialize(&contract_id.inner()))?.unwrap();
  154. let mut contract: ContractData = deserialize(&data).unwrap();
  155. contract.locked = true;
  156. contracts_tree.insert(serialize(&contract_id.inner()), serialize(&contract))?;
  157. }
  158. Ok(())
  159. })
  160. .map_err(|e: TransactionError<sled::Error>| {
  161. io::Error::other(format!("sled transaction error: {e}"))
  162. })?;
  163. info!(
  164. target: "explorer::append_block",
  165. "Appended block {} ({} bytes header, {} txs)",
  166. block.header.height,
  167. header_data.len(),
  168. block.txs.len(),
  169. );
  170. // Update stats
  171. let block_size = header_data.len() as u64 + (current_tx_offset - tx_blob_offset);
  172. self.update_stats_for_block(
  173. block.header.timestamp.inner(),
  174. block.txs.len() as u64,
  175. block_size,
  176. )
  177. .await?;
  178. Ok(())
  179. }
  180. /// Revert n blocks from the database
  181. pub async fn revert_blocks(&self, count: u64) -> io::Result<()> {
  182. if count == 0 {
  183. return Ok(())
  184. }
  185. let reader = self.tapes_db.reader();
  186. let current_len = reader.fixed_sized_tape_len(&self.database.block_index).unwrap_or(0);
  187. if count > current_len {
  188. return Err(io::Error::new(
  189. io::ErrorKind::InvalidInput,
  190. "Cannot revert more blocks than exist",
  191. ))
  192. }
  193. let new_block_count = current_len - count;
  194. let current_tx_idx_len = reader.fixed_sized_tape_len(&self.database.tx_index).unwrap_or(0);
  195. // Collect data to remove from sled
  196. let mut header_hashes_to_remove: Vec<Vec<u8>> = Vec::new();
  197. let mut tx_hashes_to_remove: Vec<Vec<u8>> = Vec::new();
  198. let mut contracts_to_remove: Vec<ContractId> = Vec::new();
  199. for height in new_block_count..current_len {
  200. // Get the block index to find transactions
  201. let block_idx = reader
  202. .read_entry(&self.database.block_index, height)?
  203. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "block index not found"))?;
  204. // Read header to get its hash
  205. let mut header_data = vec![0u8; block_idx.length as usize];
  206. reader.read_bytes(&self.database.blocks, block_idx.offset, &mut header_data)?;
  207. let header: Header = deserialize_async(&header_data).await?;
  208. header_hashes_to_remove.push(serialize_async(&header.hash()).await);
  209. // Read each tx to get its hash and check for contract deployments
  210. for i in 0..block_idx.tx_count {
  211. let tx_idx = reader
  212. .read_entry(&self.database.tx_index, block_idx.tx_start_idx + i)?
  213. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  214. let mut tx_data = vec![0u8; tx_idx.length as usize];
  215. reader.read_bytes(&self.database.transactions, tx_idx.offset, &mut tx_data)?;
  216. let transaction: Transaction = deserialize_async(&tx_data).await?;
  217. tx_hashes_to_remove.push(transaction.hash().0.to_vec());
  218. // Check for contract deployments to remove
  219. for call in &transaction.calls {
  220. if call.data.contract_id == *DEPLOYOOOR_CONTRACT_ID &&
  221. call.data.data[0] == DeployFunction::DeployV1 as u8
  222. {
  223. let params: DeployParamsV1 =
  224. deserialize_async(&call.data.data[1..]).await?;
  225. contracts_to_remove.push(ContractId::derive_public(params.public_key));
  226. }
  227. }
  228. }
  229. }
  230. let (new_block_blob_len, new_tx_idx_len, new_tx_blob_len) = if new_block_count == 0 {
  231. (0, 0, 0)
  232. } else {
  233. let last_block_idx = reader
  234. .read_entry(&self.database.block_index, new_block_count - 1)?
  235. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "block index not found"))?;
  236. let new_block_blob_len = last_block_idx.offset + last_block_idx.length;
  237. let new_tx_idx_len = last_block_idx.tx_start_idx + last_block_idx.tx_count;
  238. let new_tx_blob_len = if new_tx_idx_len == 0 {
  239. 0
  240. } else {
  241. let last_tx_idx =
  242. reader.read_entry(&self.database.tx_index, new_tx_idx_len - 1)?.ok_or_else(
  243. || io::Error::new(io::ErrorKind::NotFound, "tx index not found"),
  244. )?;
  245. last_tx_idx.offset + last_tx_idx.length
  246. };
  247. (new_block_blob_len, new_tx_idx_len, new_tx_blob_len)
  248. };
  249. // Drop the reader before truncating
  250. drop(reader);
  251. let mut truncate_tx = self.tapes_db.truncate();
  252. truncate_tx.drop_from_fixed_sized_tape(&self.database.block_index, count);
  253. truncate_tx.drop_from_fixed_sized_tape(&self.database.difficulty_index, count);
  254. let tx_idx_to_remove = current_tx_idx_len - new_tx_idx_len;
  255. truncate_tx.drop_from_fixed_sized_tape(&self.database.tx_index, tx_idx_to_remove);
  256. truncate_tx.set_blob_tape_len(&self.database.blocks, new_block_blob_len);
  257. truncate_tx.set_blob_tape_len(&self.database.transactions, new_tx_blob_len);
  258. truncate_tx.commit(Persistence::SyncData)?;
  259. // Atomic sled transaction for removing tx, header indices, and contracts
  260. (&self.tx_indices, &self.header_indices, &self.contracts)
  261. .transaction(|(tx_tree, header_tree, contracts_tree)| {
  262. for tx_hash in &tx_hashes_to_remove {
  263. tx_tree.remove(tx_hash.as_slice())?;
  264. }
  265. for header_hash in &header_hashes_to_remove {
  266. header_tree.remove(header_hash.as_slice())?;
  267. }
  268. for contract_id in &contracts_to_remove {
  269. contracts_tree.remove(serialize(&contract_id.inner()))?;
  270. }
  271. Ok(())
  272. })
  273. .map_err(|e: TransactionError<sled::Error>| {
  274. io::Error::other(format!("sled transaction error: {e}"))
  275. })?;
  276. info!(
  277. target: "explorer::revert_blocks",
  278. "Reverted {} blocks (new height: {})",
  279. count,
  280. if new_block_count == 0 { 0 } else { new_block_count - 1 }
  281. );
  282. // Rebuild stats from scratch after reorg
  283. self.rebuild_stats().await?;
  284. Ok(())
  285. }
  286. /// Revert to a specific height (keep blocks 0..=target_height)
  287. pub async fn revert_to_height(&self, target_height: u64) -> io::Result<()> {
  288. let current_height = self.get_height()?.unwrap_or(0);
  289. if target_height >= current_height {
  290. return Ok(())
  291. }
  292. self.revert_blocks(current_height - target_height).await
  293. }
  294. /// Get the current known blockchain height
  295. pub fn get_height(&self) -> io::Result<Option<u64>> {
  296. let reader = self.tapes_db.reader();
  297. let len = reader.fixed_sized_tape_len(&self.database.block_index);
  298. Ok(len.filter(|&l| l > 0).map(|l| l - 1))
  299. }
  300. /// Get the difficulty and cumulative difficulty for a block height
  301. pub fn get_difficulty(&self, height: u64) -> io::Result<Option<DifficultyIndex>> {
  302. let reader = self.tapes_db.reader();
  303. reader.read_entry(&self.database.difficulty_index, height)
  304. }
  305. /// Get the block header for a height
  306. pub async fn get_header(&self, height: u64) -> io::Result<Option<Header>> {
  307. let reader = self.tapes_db.reader();
  308. let idx = match reader.read_entry(&self.database.block_index, height)? {
  309. Some(idx) => idx,
  310. None => return Ok(None),
  311. };
  312. let mut data = vec![0u8; idx.length as usize];
  313. reader.read_bytes(&self.database.blocks, idx.offset, &mut data)?;
  314. Ok(Some(deserialize_async(&data).await?))
  315. }
  316. /// Get all the transactions in a given block height
  317. pub async fn get_block_txs(&self, height: u64) -> io::Result<Option<Vec<Transaction>>> {
  318. let reader = self.tapes_db.reader();
  319. let block_idx = match reader.read_entry(&self.database.block_index, height)? {
  320. Some(idx) => idx,
  321. None => return Ok(None),
  322. };
  323. if block_idx.tx_count == 0 {
  324. return Ok(Some(vec![]))
  325. }
  326. // Read all TxIndex entries for this block
  327. let mut tx_indices = Vec::with_capacity(block_idx.tx_count as usize);
  328. for i in 0..block_idx.tx_count {
  329. let tx_idx = reader
  330. .read_entry(&self.database.tx_index, block_idx.tx_start_idx + i)?
  331. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  332. tx_indices.push(tx_idx);
  333. }
  334. if tx_indices.is_empty() {
  335. return Ok(Some(vec![]))
  336. }
  337. // Since transactions are stored contiguously, read all data at once
  338. let first_tx = &tx_indices[0];
  339. let last_tx = &tx_indices[tx_indices.len() - 1];
  340. let total_len = (last_tx.offset + last_tx.length - first_tx.offset) as usize;
  341. // Read all transaction data in one operation
  342. let mut all_tx_data = vec![0u8; total_len];
  343. reader.read_bytes(&self.database.transactions, first_tx.offset, &mut all_tx_data)?;
  344. // Deserialize each transaction from the combined buffer
  345. let mut txs = Vec::with_capacity(tx_indices.len());
  346. for tx_idx in &tx_indices {
  347. let start = (tx_idx.offset - first_tx.offset) as usize;
  348. let end = start + tx_idx.length as usize;
  349. txs.push(deserialize_async(&all_tx_data[start..end]).await?);
  350. }
  351. Ok(Some(txs))
  352. }
  353. /// Get and construct the entire block for a given height.
  354. pub async fn get_block(&self, height: u64) -> io::Result<Option<BlockInfo>> {
  355. let header = match self.get_header(height).await? {
  356. Some(h) => h,
  357. None => return Ok(None),
  358. };
  359. let txs = self.get_block_txs(height).await?.unwrap_or_default();
  360. // We don't care about displaying the block signature.
  361. Ok(Some(BlockInfo { header, txs, signature: Signature::dummy() }))
  362. }
  363. /// Get basic block info without loading all transactions.
  364. /// Returns (header, tx_count, total_size) for efficient latest_blocks display.
  365. pub async fn get_block_summary(&self, height: u64) -> io::Result<Option<(Header, u64, u64)>> {
  366. let reader = self.tapes_db.reader();
  367. let block_idx = match reader.read_entry(&self.database.block_index, height)? {
  368. Some(idx) => idx,
  369. None => return Ok(None),
  370. };
  371. let mut header_data = vec![0u8; block_idx.length as usize];
  372. reader.read_bytes(&self.database.blocks, block_idx.offset, &mut header_data)?;
  373. let header: Header = deserialize_async(&header_data).await?;
  374. // Calculate total size: header + all transactions
  375. let total_tx_size = if block_idx.tx_count == 0 {
  376. 0
  377. } else {
  378. let first_tx_idx = reader
  379. .read_entry(&self.database.tx_index, block_idx.tx_start_idx)?
  380. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  381. let last_tx_idx = reader
  382. .read_entry(
  383. &self.database.tx_index,
  384. block_idx.tx_start_idx + block_idx.tx_count - 1,
  385. )?
  386. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  387. last_tx_idx.offset + last_tx_idx.length - first_tx_idx.offset
  388. };
  389. let total_size = block_idx.length + total_tx_size;
  390. Ok(Some((header, block_idx.tx_count, total_size)))
  391. }
  392. /// Get a transaction by its hash.
  393. /// Returns the transaction and the block height it belongs to.
  394. pub async fn get_tx_by_hash(
  395. &self,
  396. tx_hash: &[u8; 32],
  397. ) -> io::Result<Option<(Transaction, u64)>> {
  398. // Look up the tx_index position from sled
  399. let tx_idx_pos = match self.tx_indices.get(tx_hash)? {
  400. Some(pos_bytes) => {
  401. let bytes: [u8; 8] = pos_bytes.as_ref().try_into().map_err(|_| {
  402. io::Error::new(io::ErrorKind::InvalidData, "invalid tx index position")
  403. })?;
  404. u64::from_le_bytes(bytes)
  405. }
  406. None => return Ok(None),
  407. };
  408. // Read the TxIndex from tapes
  409. let reader = self.tapes_db.reader();
  410. let tx_idx = match reader.read_entry(&self.database.tx_index, tx_idx_pos)? {
  411. Some(idx) => idx,
  412. None => return Ok(None),
  413. };
  414. // Read the transaction data from the blob tape
  415. let mut data = vec![0u8; tx_idx.length as usize];
  416. reader.read_bytes(&self.database.transactions, tx_idx.offset, &mut data)?;
  417. let transaction: Transaction = deserialize_async(&data).await?;
  418. Ok(Some((transaction, tx_idx.block_height)))
  419. }
  420. /// Get a transaction by its hash string (hex encoded).
  421. pub async fn get_tx_by_hash_str(
  422. &self,
  423. tx_hash_str: &str,
  424. ) -> io::Result<Option<(Transaction, u64)>> {
  425. let hash_bytes = hex::decode(tx_hash_str)
  426. .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid hex string"))?;
  427. if hash_bytes.len() != 32 {
  428. return Err(io::Error::new(io::ErrorKind::InvalidInput, "hash must be 32 bytes"));
  429. }
  430. let mut tx_hash = [0u8; 32];
  431. tx_hash.copy_from_slice(&hash_bytes);
  432. self.get_tx_by_hash(&tx_hash).await
  433. }
  434. /// Scan a block's transactions for contract deployments and locks.
  435. /// Returns (new_contracts, locked_contract_ids)
  436. async fn scan_contract_calls(
  437. &self,
  438. block: &BlockInfo,
  439. block_height: u64,
  440. ) -> (Vec<ContractData>, Vec<ContractId>) {
  441. let mut new_contracts = Vec::new();
  442. let mut locked_contracts = Vec::new();
  443. for transaction in &block.txs {
  444. let tx_hash = *transaction.hash().inner();
  445. for call in &transaction.calls {
  446. // Check if this is a call to Deployoor
  447. if call.data.contract_id != *DEPLOYOOOR_CONTRACT_ID {
  448. continue;
  449. }
  450. let func = call.data.data[0];
  451. if func == DeployFunction::DeployV1 as u8 {
  452. let params: DeployParamsV1 =
  453. deserialize_async(&call.data.data[1..]).await.unwrap();
  454. let contract_id = ContractId::derive_public(params.public_key);
  455. info!(
  456. target: "explorer::scan_contract_calls",
  457. "Found contract deployment: {} (size: {} bytes)",
  458. contract_id, params.wasm_bincode.len(),
  459. );
  460. new_contracts.push(ContractData {
  461. contract_id,
  462. locked: false,
  463. wasm_size: params.wasm_bincode.len() as u64,
  464. deploy_block: block_height,
  465. deploy_tx_hash: tx_hash,
  466. });
  467. } else if func == DeployFunction::LockV1 as u8 {
  468. let params: LockParamsV1 =
  469. deserialize_async(&call.data.data[1..]).await.unwrap();
  470. let contract_id = ContractId::derive_public(params.public_key);
  471. info!(
  472. target: "explorer::scan_contract_calls",
  473. "Found contract lock: {}", contract_id,
  474. );
  475. locked_contracts.push(contract_id);
  476. }
  477. }
  478. }
  479. (new_contracts, locked_contracts)
  480. }
  481. /// Get a contract by its ID string (base58 encoded).
  482. pub async fn get_contract(&self, contract_id_str: &str) -> io::Result<Option<ContractData>> {
  483. let Ok(contract_id) = ContractId::from_str(contract_id_str) else {
  484. return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid contract ID"))
  485. };
  486. match self.contracts.get(serialize_async(&contract_id.inner()).await)? {
  487. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  488. None => Ok(None),
  489. }
  490. }
  491. /// List all contracts, optionally filtered by locked status.
  492. pub async fn list_contracts(
  493. &self,
  494. locked_filter: Option<bool>,
  495. ) -> io::Result<Vec<ContractData>> {
  496. let mut contracts = Vec::new();
  497. for result in self.contracts.iter() {
  498. let (_, value) = result?;
  499. let contract: ContractData = deserialize_async(&value).await?;
  500. if let Some(filter) = locked_filter {
  501. if contract.locked == filter {
  502. contracts.push(contract);
  503. }
  504. } else {
  505. contracts.push(contract);
  506. }
  507. }
  508. Ok(contracts)
  509. }
  510. /// Get the total number of contracts.
  511. pub fn get_contract_count(&self) -> io::Result<u64> {
  512. Ok(self.contracts.len() as u64)
  513. }
  514. }
  515. /// Daily statistics aggregate
  516. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  517. pub struct DailyStats {
  518. pub block_count: u64,
  519. pub user_tx_count: u64, // excluding coinbase
  520. pub total_size: u64,
  521. }
  522. /// Monthly statistics aggregate
  523. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  524. pub struct MonthlyStats {
  525. pub block_count: u64,
  526. pub total_size: u64,
  527. }
  528. impl Explorer {
  529. /// Update stats for a single block
  530. pub async fn update_stats_for_block(
  531. &self,
  532. timestamp: u64,
  533. tx_count: u64,
  534. block_size: u64,
  535. ) -> io::Result<()> {
  536. let day = Self::day_from_timestamp(timestamp);
  537. let (year, month) = Self::year_month_from_timestamp(timestamp);
  538. let user_tx = tx_count.saturating_sub(1); // exclude coinbase
  539. // Update daily stats
  540. let daily_key = format!("daily:{}", day);
  541. let mut daily = self.get_daily_stats(day).await?.unwrap_or(DailyStats {
  542. block_count: 0,
  543. user_tx_count: 0,
  544. total_size: 0,
  545. });
  546. daily.block_count += 1;
  547. daily.user_tx_count += user_tx;
  548. daily.total_size += block_size;
  549. self.stats.insert(daily_key.as_bytes(), serialize_async(&daily).await)?;
  550. // Update monthly stats
  551. let monthly_key = format!("monthly:{}:{:02}", year, month);
  552. let mut monthly = self
  553. .get_monthly_stats(year, month)
  554. .await?
  555. .unwrap_or(MonthlyStats { block_count: 0, total_size: 0 });
  556. monthly.block_count += 1;
  557. monthly.total_size += block_size;
  558. self.stats.insert(monthly_key.as_bytes(), serialize_async(&monthly).await)?;
  559. Ok(())
  560. }
  561. /// Get daily stats for a specific day
  562. pub async fn get_daily_stats(&self, day: u64) -> io::Result<Option<DailyStats>> {
  563. let key = format!("daily:{}", day);
  564. match self.stats.get(key.as_bytes())? {
  565. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  566. None => Ok(None),
  567. }
  568. }
  569. /// Get monthly stats for a specific year/month
  570. pub async fn get_monthly_stats(
  571. &self,
  572. year: u32,
  573. month: u32,
  574. ) -> io::Result<Option<MonthlyStats>> {
  575. let key = format!("monthly:{}:{:02}", year, month);
  576. match self.stats.get(key.as_bytes())? {
  577. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  578. None => Ok(None),
  579. }
  580. }
  581. /// Get all daily stats (for graph generation)
  582. pub async fn get_all_daily_stats(&self) -> io::Result<Vec<(u64, DailyStats)>> {
  583. let mut result = Vec::new();
  584. let prefix = b"daily:";
  585. for item in self.stats.scan_prefix(prefix) {
  586. let (key, value) = item?;
  587. let key_str = String::from_utf8_lossy(&key);
  588. if let Some(day_str) = key_str.strip_prefix("daily:") {
  589. if let Ok(day) = day_str.parse::<u64>() {
  590. let stats = deserialize_async(&value).await?;
  591. result.push((day, stats));
  592. }
  593. }
  594. }
  595. result.sort_by_key(|(day, _)| *day);
  596. Ok(result)
  597. }
  598. /// Get all monthly stats (for table generation)
  599. pub async fn get_all_monthly_stats(&self) -> io::Result<Vec<(u32, u32, MonthlyStats)>> {
  600. let mut result = Vec::new();
  601. let prefix = b"monthly:";
  602. for item in self.stats.scan_prefix(prefix) {
  603. let (key, value) = item?;
  604. let key_str = String::from_utf8_lossy(&key);
  605. if let Some(ym_str) = key_str.strip_prefix("monthly:") {
  606. let parts: Vec<&str> = ym_str.split(':').collect();
  607. if parts.len() == 2 {
  608. if let (Ok(year), Ok(month)) =
  609. (parts[0].parse::<u32>(), parts[1].parse::<u32>())
  610. {
  611. let stats = deserialize_async(&value).await?;
  612. result.push((year, month, stats));
  613. }
  614. }
  615. }
  616. }
  617. result.sort_by_key(|(year, month, _)| (*year, *month));
  618. Ok(result)
  619. }
  620. /// Clear all stats (called before rebuilding after reorg)
  621. pub fn clear_stats(&self) -> io::Result<()> {
  622. // Clear daily stats
  623. let daily_keys: Vec<_> =
  624. self.stats.scan_prefix(b"daily:").filter_map(|r| r.ok().map(|(k, _)| k)).collect();
  625. for key in daily_keys {
  626. self.stats.remove(&key)?;
  627. }
  628. // Clear monthly stats
  629. let monthly_keys: Vec<_> =
  630. self.stats.scan_prefix(b"monthly:").filter_map(|r| r.ok().map(|(k, _)| k)).collect();
  631. for key in monthly_keys {
  632. self.stats.remove(&key)?;
  633. }
  634. Ok(())
  635. }
  636. /// Rebuild all stats from blockchain data
  637. pub async fn rebuild_stats(&self) -> io::Result<()> {
  638. info!(target: "explorer::rebuild_stats", "Clearing existing stats...");
  639. self.clear_stats()?;
  640. let height = match self.get_height()? {
  641. Some(h) => h,
  642. None => return Ok(()), // No blocks yet
  643. };
  644. info!(target: "explorer::rebuild_stats", "Rebuilding stats for {} blocks...", height + 1);
  645. let reader = self.tapes_db.reader();
  646. for h in 0..=height {
  647. let block_idx = match reader.read_entry(&self.database.block_index, h)? {
  648. Some(idx) => idx,
  649. None => continue,
  650. };
  651. // Read header to get timestamp
  652. let mut header_data = vec![0u8; block_idx.length as usize];
  653. reader.read_bytes(&self.database.blocks, block_idx.offset, &mut header_data)?;
  654. let header: Header = deserialize_async(&header_data).await?;
  655. // Calculate block size
  656. let tx_size = if block_idx.tx_count == 0 {
  657. 0
  658. } else {
  659. let first_tx_idx = reader
  660. .read_entry(&self.database.tx_index, block_idx.tx_start_idx)?
  661. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  662. let last_tx_idx = reader
  663. .read_entry(
  664. &self.database.tx_index,
  665. block_idx.tx_start_idx + block_idx.tx_count - 1,
  666. )?
  667. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  668. last_tx_idx.offset + last_tx_idx.length - first_tx_idx.offset
  669. };
  670. let block_size = block_idx.length + tx_size;
  671. self.update_stats_for_block(header.timestamp.inner(), block_idx.tx_count, block_size)
  672. .await?;
  673. }
  674. info!(target: "explorer::rebuild_stats", "Stats rebuild complete");
  675. Ok(())
  676. }
  677. /// Get day number from unix timestamp (days since epoch)
  678. fn day_from_timestamp(timestamp: u64) -> u64 {
  679. timestamp / 86400
  680. }
  681. /// Get year and month from unix timestamp
  682. fn year_month_from_timestamp(timestamp: u64) -> (u32, u32) {
  683. // Days since epoch
  684. let days = timestamp / 86400;
  685. // Approximate year (will be corrected)
  686. let mut year = 1970u32;
  687. let mut remaining_days = days as i64;
  688. loop {
  689. let days_in_year = if year.is_multiple_of(4) &&
  690. (!year.is_multiple_of(100) || year.is_multiple_of(400))
  691. {
  692. 366i64
  693. } else {
  694. 365i64
  695. };
  696. if remaining_days < days_in_year {
  697. break;
  698. }
  699. remaining_days -= days_in_year;
  700. year += 1;
  701. }
  702. // Now find month
  703. let is_leap =
  704. year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400));
  705. let days_in_months: [i64; 12] = if is_leap {
  706. [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  707. } else {
  708. [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  709. };
  710. let mut month = 1u32;
  711. for days_in_month in days_in_months.iter() {
  712. if remaining_days < *days_in_month {
  713. break;
  714. }
  715. remaining_days -= days_in_month;
  716. month += 1;
  717. }
  718. (year, month)
  719. }
  720. }