// Copyright (C) 2024 Riften Labs AS // // This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html use anyhow::Result; use bitcoin_hashes::hex::{FromHex, ToHex}; use bitcoincash::{consensus::deserialize, Block, BlockHash}; use chain::{get_new_headers, Chain, StoreBlockUndoer}; use db::{get_token_tvl, historic_price, list_tokens_by_volume, load_all_headers, store_headers}; use electrum::electrum_get_tip; use electrum_client::{Client, ElectrumApi, Param}; use log::{debug, error, info}; 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 rocket_cors::{AllowedHeaders, AllowedOrigins}; use rusqlite::Connection; use serde_json::Value; use std::{ backtrace::Backtrace, panic, path::Path, process, sync::{mpsc::sync_channel, Arc, Mutex}, thread, time::{Duration, SystemTime, UNIX_EPOCH}, }; use stderrlog::LogLevelNum; use crate::db::{ config_get, config_set, insert_utxo_funding, insert_utxo_spending, prepare_tables, }; // The block where first cauldron contract was deployed. (Block 799870) const RIFTEN_LABS_GENESIS_BLOCK: &str = "000000000000000000ed24c811077f7268a21ecf25cb437655aaba33d8ff4997"; // Last indexed block height. const KEY_LAST_INDEXED: &str = "last_indexed"; mod chain; mod db; mod electrum; 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 index_blocks( chain: Arc>, conn: Arc>, client: Arc>, ) -> Result { let (tip_header, _) = electrum_get_tip(&client.lock().unwrap())?; let (block_send, block_recv) = sync_channel::>(10); // Update header chain (and undo any blocks that may have reorged away) { let chain = chain.lock().unwrap(); if tip_header.block_hash() != chain.tip_hash() { debug!( "Updating header chain from {} to {}", chain.tip_hash().to_hex(), tip_header.block_hash().to_hex() ); let new_headers = get_new_headers(&client.lock().unwrap(), &chain, &tip_header.block_hash())?; debug!("Storing headers"); store_headers(&conn.lock().unwrap(), &new_headers)?; let undoer = StoreBlockUndoer::new(conn.clone())?; chain.update(undoer, new_headers, None)?; debug!("Header update done"); } } let conn_cpy = conn.clone(); thread::spawn(move || { let conn = conn_cpy; let last_indexed = config_get(&conn.lock().unwrap(), KEY_LAST_INDEXED).unwrap(); let last_indexed = last_indexed.unwrap_or(RIFTEN_LABS_GENESIS_BLOCK.to_string()); let mut last_indexed = BlockHash::from_hex(&last_indexed).unwrap(); loop { if tip_header.block_hash() == last_indexed { block_send.send(None).unwrap(); break; } let next_height = chain .lock() .unwrap() .get_block_height(&last_indexed) .unwrap() + 1; let res = client .lock() .unwrap() .raw_call("blockchain.block.get", vec![Param::U32(next_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(); let block_hash = block.block_hash(); block_send .send(Some(( next_height, chain.lock().unwrap().get_mtp(next_height).unwrap(), block, ))) .unwrap(); last_indexed = block_hash; } }); loop { let (block_height, mtp, block) = match block_recv.recv()? { Some(res) => res, None => break, }; let mut lock = conn.lock().unwrap(); let tx = lock.transaction()?; let cauldrons = parse_cauldrons(&block); let blockhash = block.block_hash(); insert_utxo_funding(&tx, mtp as u32, &cauldrons, &blockhash)?; insert_utxo_spending(&tx, mtp as u32, &cauldrons, &blockhash)?; config_set(&tx, KEY_LAST_INDEXED, &blockhash.to_hex()); tx.commit()?; println!( "Indexed {}; mtp: {}, height {}, {} trades.", block.header.block_hash().to_hex(), mtp, block_height, cauldrons.len() ); } Ok(tip_header.block_hash()) } #[derive(Serialize)] #[serde(crate = "rocket::serde")] struct TVLResponse {} #[get("/tvl/