db.rs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  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 kvdb_overlay::Batch;
  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 kvdb
  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 kvdb 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 kvdb write for tx_indices, header_indices, and contracts
  141. let mut tx_indices_batch = Batch::new();
  142. let mut header_indices_batch = Batch::new();
  143. let mut contracts_batch = Batch::new();
  144. // Insert all transaction indices
  145. for (hash, idx) in &tx_entries {
  146. tx_indices_batch.insert(hash.as_slice(), idx.as_slice());
  147. }
  148. // Insert header hash -> height mapping
  149. header_indices_batch.insert(header_hash.as_slice(), height_bytes.as_slice());
  150. // Insert new contracts
  151. for contract in &new_contracts {
  152. contracts_batch.insert(&serialize(&contract.contract_id.inner()), &serialize(contract));
  153. }
  154. // Update locked contracts
  155. for contract_id in &locked_contracts {
  156. let data = self.contracts.get(&serialize(&contract_id.inner()))?.unwrap();
  157. let mut contract: ContractData = deserialize(&data).unwrap();
  158. contract.locked = true;
  159. contracts_batch.insert(&serialize(&contract_id.inner()), &serialize(&contract));
  160. }
  161. self.kvdb.atomic_write(&[
  162. (&self.tx_indices, &tx_indices_batch),
  163. (&self.header_indices, &header_indices_batch),
  164. (&self.contracts, &contracts_batch),
  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 kvdb
  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 kvdb write for removing tx, header indices, and contracts
  263. let mut tx_indices_batch = Batch::new();
  264. for tx_hash in &tx_hashes_to_remove {
  265. tx_indices_batch.remove(tx_hash.as_slice());
  266. }
  267. let mut header_indices_batch = Batch::new();
  268. for header_hash in &header_hashes_to_remove {
  269. header_indices_batch.remove(header_hash.as_slice());
  270. }
  271. let mut contracts_batch = Batch::new();
  272. for contract_id in &contracts_to_remove {
  273. contracts_batch.remove(&serialize(&contract_id.inner()));
  274. }
  275. self.kvdb.atomic_write(&[
  276. (&self.tx_indices, &tx_indices_batch),
  277. (&self.header_indices, &header_indices_batch),
  278. (&self.contracts, &contracts_batch),
  279. ])?;
  280. // Rebuild stats from scratch after reorg
  281. self.rebuild_stats().await?;
  282. Ok(())
  283. }
  284. /// Revert to a specific height (keep blocks 0..=target_height)
  285. pub async fn revert_to_height(&self, target_height: u64) -> io::Result<()> {
  286. let current_height = self.get_height()?.unwrap_or(0);
  287. if target_height >= current_height {
  288. return Ok(())
  289. }
  290. self.revert_blocks(current_height - target_height).await
  291. }
  292. /// Get the current known blockchain height
  293. pub fn get_height(&self) -> io::Result<Option<u64>> {
  294. let reader = self.tapes_db.reader();
  295. let len = reader.fixed_sized_tape_len(&self.database.block_index);
  296. Ok(len.filter(|&l| l > 0).map(|l| l - 1))
  297. }
  298. /// Get the difficulty and cumulative difficulty for a block height
  299. pub fn get_difficulty(&self, height: u64) -> io::Result<Option<DifficultyIndex>> {
  300. let reader = self.tapes_db.reader();
  301. reader.read_entry(&self.database.difficulty_index, height)
  302. }
  303. /// Get the block header for a height
  304. pub async fn get_header(&self, height: u64) -> io::Result<Option<Header>> {
  305. let reader = self.tapes_db.reader();
  306. let idx = match reader.read_entry(&self.database.block_index, height)? {
  307. Some(idx) => idx,
  308. None => return Ok(None),
  309. };
  310. let mut data = vec![0u8; idx.length as usize];
  311. reader.read_bytes(&self.database.blocks, idx.offset, &mut data)?;
  312. Ok(Some(deserialize_async(&data).await?))
  313. }
  314. /// Get all the transactions in a given block height
  315. pub async fn get_block_txs(&self, height: u64) -> io::Result<Option<Vec<Transaction>>> {
  316. let reader = self.tapes_db.reader();
  317. let block_idx = match reader.read_entry(&self.database.block_index, height)? {
  318. Some(idx) => idx,
  319. None => return Ok(None),
  320. };
  321. if block_idx.tx_count == 0 {
  322. return Ok(Some(vec![]))
  323. }
  324. // Read all TxIndex entries for this block
  325. let mut tx_indices = Vec::with_capacity(block_idx.tx_count as usize);
  326. for i in 0..block_idx.tx_count {
  327. let tx_idx = reader
  328. .read_entry(&self.database.tx_index, block_idx.tx_start_idx + i)?
  329. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  330. tx_indices.push(tx_idx);
  331. }
  332. if tx_indices.is_empty() {
  333. return Ok(Some(vec![]))
  334. }
  335. // Since transactions are stored contiguously, read all data at once
  336. let first_tx = &tx_indices[0];
  337. let last_tx = &tx_indices[tx_indices.len() - 1];
  338. let total_len = (last_tx.offset + last_tx.length - first_tx.offset) as usize;
  339. // Read all transaction data in one operation
  340. let mut all_tx_data = vec![0u8; total_len];
  341. reader.read_bytes(&self.database.transactions, first_tx.offset, &mut all_tx_data)?;
  342. // Deserialize each transaction from the combined buffer
  343. let mut txs = Vec::with_capacity(tx_indices.len());
  344. for tx_idx in &tx_indices {
  345. let start = (tx_idx.offset - first_tx.offset) as usize;
  346. let end = start + tx_idx.length as usize;
  347. txs.push(deserialize_async(&all_tx_data[start..end]).await?);
  348. }
  349. Ok(Some(txs))
  350. }
  351. /// Get and construct the entire block for a given height.
  352. pub async fn get_block(&self, height: u64) -> io::Result<Option<BlockInfo>> {
  353. let header = match self.get_header(height).await? {
  354. Some(h) => h,
  355. None => return Ok(None),
  356. };
  357. let txs = self.get_block_txs(height).await?.unwrap_or_default();
  358. // We don't care about displaying the block signature.
  359. Ok(Some(BlockInfo { header, txs, signature: Signature::dummy() }))
  360. }
  361. /// Get basic block info without loading all transactions.
  362. /// Returns (header, tx_count, total_size) for efficient latest_blocks display.
  363. pub async fn get_block_summary(&self, height: u64) -> io::Result<Option<(Header, u64, u64)>> {
  364. let reader = self.tapes_db.reader();
  365. let block_idx = match reader.read_entry(&self.database.block_index, height)? {
  366. Some(idx) => idx,
  367. None => return Ok(None),
  368. };
  369. let mut header_data = vec![0u8; block_idx.length as usize];
  370. reader.read_bytes(&self.database.blocks, block_idx.offset, &mut header_data)?;
  371. let header: Header = deserialize_async(&header_data).await?;
  372. // Calculate total size: header + all transactions
  373. let total_tx_size = if block_idx.tx_count == 0 {
  374. 0
  375. } else {
  376. let first_tx_idx = reader
  377. .read_entry(&self.database.tx_index, block_idx.tx_start_idx)?
  378. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  379. let last_tx_idx = reader
  380. .read_entry(
  381. &self.database.tx_index,
  382. block_idx.tx_start_idx + block_idx.tx_count - 1,
  383. )?
  384. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  385. last_tx_idx.offset + last_tx_idx.length - first_tx_idx.offset
  386. };
  387. let total_size = block_idx.length + total_tx_size;
  388. Ok(Some((header, block_idx.tx_count, total_size)))
  389. }
  390. /// Get a transaction by its hash.
  391. /// Returns the transaction and the block height it belongs to.
  392. pub async fn get_tx_by_hash(
  393. &self,
  394. tx_hash: &[u8; 32],
  395. ) -> io::Result<Option<(Transaction, u64)>> {
  396. // Look up the tx_index position from kvdb
  397. let tx_idx_pos = match self.tx_indices.get(tx_hash)? {
  398. Some(pos_bytes) => {
  399. let bytes: [u8; 8] = pos_bytes.try_into().map_err(|_| {
  400. io::Error::new(io::ErrorKind::InvalidData, "invalid tx index position")
  401. })?;
  402. u64::from_le_bytes(bytes)
  403. }
  404. None => return Ok(None),
  405. };
  406. // Read the TxIndex from tapes
  407. let reader = self.tapes_db.reader();
  408. let tx_idx = match reader.read_entry(&self.database.tx_index, tx_idx_pos)? {
  409. Some(idx) => idx,
  410. None => return Ok(None),
  411. };
  412. // Read the transaction data from the blob tape
  413. let mut data = vec![0u8; tx_idx.length as usize];
  414. reader.read_bytes(&self.database.transactions, tx_idx.offset, &mut data)?;
  415. let transaction: Transaction = deserialize_async(&data).await?;
  416. Ok(Some((transaction, tx_idx.block_height)))
  417. }
  418. /// Get a transaction by its hash string (hex encoded).
  419. pub async fn get_tx_by_hash_str(
  420. &self,
  421. tx_hash_str: &str,
  422. ) -> io::Result<Option<(Transaction, u64)>> {
  423. let hash_bytes = hex::decode(tx_hash_str)
  424. .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid hex string"))?;
  425. if hash_bytes.len() != 32 {
  426. return Err(io::Error::new(io::ErrorKind::InvalidInput, "hash must be 32 bytes"));
  427. }
  428. let mut tx_hash = [0u8; 32];
  429. tx_hash.copy_from_slice(&hash_bytes);
  430. self.get_tx_by_hash(&tx_hash).await
  431. }
  432. /// Scan a block's transactions for contract deployments and locks.
  433. /// Returns (new_contracts, locked_contract_ids)
  434. async fn scan_contract_calls(
  435. &self,
  436. block: &BlockInfo,
  437. block_height: u64,
  438. ) -> (Vec<ContractData>, Vec<ContractId>) {
  439. let mut new_contracts = Vec::new();
  440. let mut locked_contracts = Vec::new();
  441. for transaction in &block.txs {
  442. let tx_hash = *transaction.hash().inner();
  443. for call in &transaction.calls {
  444. // Check if this is a call to Deployoor
  445. if call.data.contract_id != *DEPLOYOOOR_CONTRACT_ID {
  446. continue;
  447. }
  448. let func = call.data.data[0];
  449. if func == DeployFunction::DeployV1 as u8 {
  450. let params: DeployParamsV1 =
  451. deserialize_async(&call.data.data[1..]).await.unwrap();
  452. let contract_id = ContractId::derive_public(params.public_key);
  453. info!(
  454. target: "explorer::scan_contract_calls",
  455. "Found contract deployment: {} (size: {} bytes)",
  456. contract_id, params.wasm_bincode.len(),
  457. );
  458. new_contracts.push(ContractData {
  459. contract_id,
  460. locked: false,
  461. wasm_size: params.wasm_bincode.len() as u64,
  462. deploy_block: block_height,
  463. deploy_tx_hash: tx_hash,
  464. });
  465. } else if func == DeployFunction::LockV1 as u8 {
  466. let params: LockParamsV1 =
  467. deserialize_async(&call.data.data[1..]).await.unwrap();
  468. let contract_id = ContractId::derive_public(params.public_key);
  469. info!(
  470. target: "explorer::scan_contract_calls",
  471. "Found contract lock: {}", contract_id,
  472. );
  473. locked_contracts.push(contract_id);
  474. }
  475. }
  476. }
  477. (new_contracts, locked_contracts)
  478. }
  479. /// Get a contract by its ID string (base58 encoded).
  480. pub async fn get_contract(&self, contract_id_str: &str) -> io::Result<Option<ContractData>> {
  481. let Ok(contract_id) = ContractId::from_str(contract_id_str) else {
  482. return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid contract ID"))
  483. };
  484. match self.contracts.get(&serialize_async(&contract_id.inner()).await)? {
  485. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  486. None => Ok(None),
  487. }
  488. }
  489. /// List all contracts, optionally filtered by locked status.
  490. pub async fn list_contracts(
  491. &self,
  492. locked_filter: Option<bool>,
  493. ) -> io::Result<Vec<ContractData>> {
  494. let mut contracts = Vec::new();
  495. for result in self.contracts.iter() {
  496. let (_, value) = result?;
  497. let contract: ContractData = deserialize_async(&value).await?;
  498. if let Some(filter) = locked_filter {
  499. if contract.locked == filter {
  500. contracts.push(contract);
  501. }
  502. } else {
  503. contracts.push(contract);
  504. }
  505. }
  506. Ok(contracts)
  507. }
  508. /// Get the total number of contracts.
  509. pub fn get_contract_count(&self) -> io::Result<u64> {
  510. Ok(self.contracts.len()? as u64)
  511. }
  512. }
  513. /// Daily statistics aggregate
  514. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  515. pub struct DailyStats {
  516. pub block_count: u64,
  517. pub user_tx_count: u64, // excluding coinbase
  518. pub total_size: u64,
  519. }
  520. /// Monthly statistics aggregate
  521. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  522. pub struct MonthlyStats {
  523. pub block_count: u64,
  524. pub total_size: u64,
  525. }
  526. impl Explorer {
  527. /// Update stats for a single block
  528. pub async fn update_stats_for_block(
  529. &self,
  530. timestamp: u64,
  531. tx_count: u64,
  532. block_size: u64,
  533. ) -> io::Result<()> {
  534. let day = Self::day_from_timestamp(timestamp);
  535. let (year, month) = Self::year_month_from_timestamp(timestamp);
  536. let user_tx = tx_count.saturating_sub(1); // exclude coinbase
  537. // Update daily stats
  538. let daily_key = format!("daily:{}", day);
  539. let mut daily = self.get_daily_stats(day).await?.unwrap_or(DailyStats {
  540. block_count: 0,
  541. user_tx_count: 0,
  542. total_size: 0,
  543. });
  544. daily.block_count += 1;
  545. daily.user_tx_count += user_tx;
  546. daily.total_size += block_size;
  547. self.stats.insert(daily_key.as_bytes(), &serialize_async(&daily).await)?;
  548. // Update monthly stats
  549. let monthly_key = format!("monthly:{}:{:02}", year, month);
  550. let mut monthly = self
  551. .get_monthly_stats(year, month)
  552. .await?
  553. .unwrap_or(MonthlyStats { block_count: 0, total_size: 0 });
  554. monthly.block_count += 1;
  555. monthly.total_size += block_size;
  556. self.stats.insert(monthly_key.as_bytes(), &serialize_async(&monthly).await)?;
  557. Ok(())
  558. }
  559. /// Get daily stats for a specific day
  560. pub async fn get_daily_stats(&self, day: u64) -> io::Result<Option<DailyStats>> {
  561. let key = format!("daily:{}", day);
  562. match self.stats.get(key.as_bytes())? {
  563. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  564. None => Ok(None),
  565. }
  566. }
  567. /// Get monthly stats for a specific year/month
  568. pub async fn get_monthly_stats(
  569. &self,
  570. year: u32,
  571. month: u32,
  572. ) -> io::Result<Option<MonthlyStats>> {
  573. let key = format!("monthly:{}:{:02}", year, month);
  574. match self.stats.get(key.as_bytes())? {
  575. Some(data) => Ok(Some(deserialize_async(&data).await?)),
  576. None => Ok(None),
  577. }
  578. }
  579. /// Get all daily stats (for graph generation)
  580. pub async fn get_all_daily_stats(&self) -> io::Result<Vec<(u64, DailyStats)>> {
  581. let mut result = Vec::new();
  582. let prefix = b"daily:";
  583. for item in self.stats.prefix_iter(prefix) {
  584. let (key, value) = item?;
  585. let key_str = String::from_utf8_lossy(&key);
  586. if let Some(day_str) = key_str.strip_prefix("daily:") {
  587. if let Ok(day) = day_str.parse::<u64>() {
  588. let stats = deserialize_async(&value).await?;
  589. result.push((day, stats));
  590. }
  591. }
  592. }
  593. result.sort_by_key(|(day, _)| *day);
  594. Ok(result)
  595. }
  596. /// Get all monthly stats (for table generation)
  597. pub async fn get_all_monthly_stats(&self) -> io::Result<Vec<(u32, u32, MonthlyStats)>> {
  598. let mut result = Vec::new();
  599. let prefix = b"monthly:";
  600. for item in self.stats.prefix_iter(prefix) {
  601. let (key, value) = item?;
  602. let key_str = String::from_utf8_lossy(&key);
  603. if let Some(ym_str) = key_str.strip_prefix("monthly:") {
  604. let parts: Vec<&str> = ym_str.split(':').collect();
  605. if parts.len() == 2 {
  606. if let (Ok(year), Ok(month)) =
  607. (parts[0].parse::<u32>(), parts[1].parse::<u32>())
  608. {
  609. let stats = deserialize_async(&value).await?;
  610. result.push((year, month, stats));
  611. }
  612. }
  613. }
  614. }
  615. result.sort_by_key(|(year, month, _)| (*year, *month));
  616. Ok(result)
  617. }
  618. /// Clear all stats (called before rebuilding after reorg)
  619. pub fn clear_stats(&self) -> io::Result<()> {
  620. // Clear daily stats
  621. let daily_keys: Vec<_> =
  622. self.stats.prefix_iter(b"daily:").filter_map(|r| r.ok().map(|(k, _)| k)).collect();
  623. for key in daily_keys {
  624. self.stats.remove(&key)?;
  625. }
  626. // Clear monthly stats
  627. let monthly_keys: Vec<_> =
  628. self.stats.prefix_iter(b"monthly:").filter_map(|r| r.ok().map(|(k, _)| k)).collect();
  629. for key in monthly_keys {
  630. self.stats.remove(&key)?;
  631. }
  632. Ok(())
  633. }
  634. /// Rebuild all stats from blockchain data
  635. pub async fn rebuild_stats(&self) -> io::Result<()> {
  636. info!(target: "explorer::rebuild_stats", "Clearing existing stats...");
  637. self.clear_stats()?;
  638. let height = match self.get_height()? {
  639. Some(h) => h,
  640. None => return Ok(()), // No blocks yet
  641. };
  642. info!(target: "explorer::rebuild_stats", "Rebuilding stats for {} blocks...", height + 1);
  643. let reader = self.tapes_db.reader();
  644. for h in 0..=height {
  645. let block_idx = match reader.read_entry(&self.database.block_index, h)? {
  646. Some(idx) => idx,
  647. None => continue,
  648. };
  649. // Read header to get timestamp
  650. let mut header_data = vec![0u8; block_idx.length as usize];
  651. reader.read_bytes(&self.database.blocks, block_idx.offset, &mut header_data)?;
  652. let header: Header = deserialize_async(&header_data).await?;
  653. // Calculate block size
  654. let tx_size = if block_idx.tx_count == 0 {
  655. 0
  656. } else {
  657. let first_tx_idx = reader
  658. .read_entry(&self.database.tx_index, block_idx.tx_start_idx)?
  659. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  660. let last_tx_idx = reader
  661. .read_entry(
  662. &self.database.tx_index,
  663. block_idx.tx_start_idx + block_idx.tx_count - 1,
  664. )?
  665. .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "tx index not found"))?;
  666. last_tx_idx.offset + last_tx_idx.length - first_tx_idx.offset
  667. };
  668. let block_size = block_idx.length + tx_size;
  669. self.update_stats_for_block(header.timestamp.inner(), block_idx.tx_count, block_size)
  670. .await?;
  671. }
  672. info!(target: "explorer::rebuild_stats", "Stats rebuild complete");
  673. Ok(())
  674. }
  675. /// Get day number from unix timestamp (days since epoch)
  676. fn day_from_timestamp(timestamp: u64) -> u64 {
  677. timestamp / 86400
  678. }
  679. /// Get year and month from unix timestamp
  680. fn year_month_from_timestamp(timestamp: u64) -> (u32, u32) {
  681. // Days since epoch
  682. let days = timestamp / 86400;
  683. // Approximate year (will be corrected)
  684. let mut year = 1970u32;
  685. let mut remaining_days = days as i64;
  686. loop {
  687. let days_in_year = if year.is_multiple_of(4) &&
  688. (!year.is_multiple_of(100) || year.is_multiple_of(400))
  689. {
  690. 366i64
  691. } else {
  692. 365i64
  693. };
  694. if remaining_days < days_in_year {
  695. break;
  696. }
  697. remaining_days -= days_in_year;
  698. year += 1;
  699. }
  700. // Now find month
  701. let is_leap =
  702. year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400));
  703. let days_in_months: [i64; 12] = if is_leap {
  704. [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  705. } else {
  706. [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  707. };
  708. let mut month = 1u32;
  709. for days_in_month in days_in_months.iter() {
  710. if remaining_days < *days_in_month {
  711. break;
  712. }
  713. remaining_days -= days_in_month;
  714. month += 1;
  715. }
  716. (year, month)
  717. }
  718. }