use anyhow::Context; use bitcoin_hashes::hex::ToHex; use bitcoincash::{consensus::deserialize, Block}; use db::get_token_tvl; use electrum_client::{Client, ElectrumApi, Param}; use rayon::prelude::*; use riftenlabs_defi::cauldron::{parse_cauldron, ParsedContract}; use rocket::{ get, http::Status, launch, response::status::Custom, routes, serde::{ json::{json, Json}, Serialize, }, State, }; use rusqlite::Connection; use serde_json::Value; use std::{ backtrace::Backtrace, collections::VecDeque, panic, path::Path, process, sync::{mpsc::sync_channel, Arc, Mutex}, thread, time::Duration, }; use crate::{ chainutil::calculate_mtp, db::{config_get, config_set, insert_utxo_funding, insert_utxo_spending, prepare_tables}, }; // The block where first cauldron contract was deployed. const GENESIS_BLOCK: i64 = 799870; // Trail behind the 10 block reorg-protection. We have not implemented logic for reorgs. const TRAIL_BEHIND_OFFSET: i64 = 10; // Last indexed block height. const KEY_LAST_INDEXED: &str = "last_indexed"; mod chainutil; mod db; fn parse_cauldrons(block: &Block) -> Vec { block .txdata .par_iter() .flat_map(|tx| { tx.input .par_iter() .enumerate() .filter_map(move |(i, _)| parse_cauldron(i, tx)) }) .collect() } fn get_tip_height(client: &Client) -> anyhow::Result { let tip: Value = serde_json::from_str(&client.raw_call("blockchain.headers.tip", [])?.to_string())?; tip.get("height") .context("no height")? .as_i64() .context("no int") } fn index_blocks(conn: Arc>, client: Arc>) -> anyhow::Result { let last_indexed = config_get(&conn.lock().unwrap(), KEY_LAST_INDEXED)?.unwrap_or(GENESIS_BLOCK - 1); let tip_height = get_tip_height(&client.lock().unwrap())?; let stop_height = tip_height - TRAIL_BEHIND_OFFSET; let mut block_height = last_indexed - 11; // 11 to get correct mtp let (block_send, block_recv) = sync_channel::>(10); thread::spawn(move || { let mut block_timestamps: VecDeque = VecDeque::with_capacity(11); loop { if block_height >= stop_height { block_send.send(None).unwrap(); break; } let res = client .lock() .unwrap() .raw_call( "blockchain.block.get", vec![Param::U32(block_height as u32)], ) .unwrap(); let block_hex: String = serde_json::from_str(&res.to_string()).unwrap(); let block: Block = deserialize(&hex::decode(&block_hex).unwrap()).unwrap(); block_timestamps.push_back(block.header.time); // Ensure the buffer only keeps the last 11 timestamps if block_timestamps.len() > 11 { block_timestamps.pop_front(); } let mtp = calculate_mtp(&block_timestamps); block_send.send(Some((block_height, mtp, block))).unwrap(); block_height += 1; } }); loop { let (block_height, mtp, block) = match block_recv.recv()? { Some(res) => res, None => break, }; if block_height <= last_indexed { continue; } let mut lock = conn.lock().unwrap(); let tx = lock.transaction()?; let cauldrons = parse_cauldrons(&block); insert_utxo_funding(&tx, mtp, &cauldrons)?; insert_utxo_spending(&tx, mtp, &cauldrons)?; config_set(&tx, KEY_LAST_INDEXED, block_height); tx.commit()?; println!( "Indexed {}; mtp: {}, height {}, {} trades.", block.header.block_hash().to_hex(), mtp, block_height, cauldrons.len() ); } Ok(tip_height) } #[derive(Serialize)] #[serde(crate = "rocket::serde")] struct TVLResponse {} #[get("/tvl/