db.rs 32 KB

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