2024-02-14 11:11:16 +01:00
|
|
|
// Copyright (C) 2024 Riften Labs AS
|
2024-02-05 16:32:42 +01:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
use anyhow::Result;
|
|
|
|
|
use bitcoincash::{consensus::deserialize, Block, BlockHash};
|
|
|
|
|
use db::{DBPool, DB};
|
|
|
|
|
use electrum::electrum_get_tip;
|
2023-11-29 12:25:04 +01:00
|
|
|
use electrum_client::{Client, ElectrumApi, Param};
|
2024-05-09 11:25:27 +02:00
|
|
|
use log::{error, info, warn};
|
|
|
|
|
use rocket::{launch, routes};
|
2024-01-19 14:38:31 +01:00
|
|
|
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
2024-05-29 08:45:50 +02:00
|
|
|
use rpc::ResponseCache;
|
2024-05-09 11:25:27 +02:00
|
|
|
use rusqlite::OpenFlags;
|
2023-11-29 12:25:04 +01:00
|
|
|
use std::{
|
|
|
|
|
backtrace::Backtrace,
|
2024-05-29 08:45:50 +02:00
|
|
|
collections::HashMap,
|
2023-11-29 12:25:04 +01:00
|
|
|
panic,
|
|
|
|
|
path::Path,
|
|
|
|
|
process,
|
2024-05-09 11:25:27 +02:00
|
|
|
sync::{Arc, Mutex},
|
2023-11-29 12:25:04 +01:00
|
|
|
thread,
|
2024-04-03 09:49:44 +02:00
|
|
|
time::Duration,
|
2023-11-29 12:25:04 +01:00
|
|
|
};
|
2024-02-14 11:11:16 +01:00
|
|
|
use stderrlog::LogLevelNum;
|
2023-11-29 12:25:04 +01:00
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
use crate::bcmr::bcmrdownloader::BCMRDownloader;
|
|
|
|
|
use crate::db::cauldron::header::load_all_headers;
|
|
|
|
|
use crate::index::{index_blocks, update_mempool};
|
2023-11-29 12:25:04 +01:00
|
|
|
|
2024-02-14 11:11:16 +01:00
|
|
|
// The block where first cauldron contract was deployed. (Block 799870)
|
2024-05-09 11:25:27 +02:00
|
|
|
#[allow(dead_code)]
|
2024-02-14 11:11:16 +01:00
|
|
|
const RIFTEN_LABS_GENESIS_BLOCK: &str =
|
|
|
|
|
"000000000000000000ed24c811077f7268a21ecf25cb437655aaba33d8ff4997";
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
// Start parsing for BCMR data from this height
|
|
|
|
|
const CASHTOKEN_ACTIVATION_HEIGHT: &str =
|
|
|
|
|
"000000000000000002b678c471841c3e404ec7ae9ca9c32026fe27eb6e3a1ed1";
|
|
|
|
|
|
2023-11-29 12:25:04 +01:00
|
|
|
// Last indexed block height.
|
|
|
|
|
const KEY_LAST_INDEXED: &str = "last_indexed";
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
mod bcmr;
|
2024-04-03 21:40:33 +02:00
|
|
|
mod cashaddr;
|
2024-02-14 11:11:16 +01:00
|
|
|
mod chain;
|
2023-11-29 12:25:04 +01:00
|
|
|
mod db;
|
2024-02-14 11:11:16 +01:00
|
|
|
mod electrum;
|
2024-05-09 11:25:27 +02:00
|
|
|
mod index;
|
2024-03-04 16:40:50 +01:00
|
|
|
mod rpc;
|
2024-04-03 09:49:44 +02:00
|
|
|
mod timeutil;
|
2023-11-29 12:25:04 +01:00
|
|
|
|
|
|
|
|
fn set_panic_hook() {
|
|
|
|
|
panic::set_hook(Box::new(|panic_info| {
|
2024-03-04 16:40:50 +01:00
|
|
|
error!("A thread panicked, terminating the program.");
|
|
|
|
|
if let Some(error) = panic_info.payload().downcast_ref::<anyhow::Error>() {
|
|
|
|
|
error!("Panic occurred: {:?}", error);
|
|
|
|
|
error!("Anyhow backtrace:\n{}", error.backtrace());
|
|
|
|
|
let mut source = error.source();
|
|
|
|
|
while let Some(cause) = source {
|
|
|
|
|
error!("Caused by: {:?}", cause);
|
|
|
|
|
source = cause.source();
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(message) = panic_info.payload().downcast_ref::<&str>() {
|
2024-02-14 11:11:16 +01:00
|
|
|
error!("Panic occurred: {}", message);
|
|
|
|
|
} else {
|
2024-03-04 16:40:50 +01:00
|
|
|
error!("Panic info: {:?}", panic_info);
|
2023-11-29 12:25:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-04 16:40:50 +01:00
|
|
|
let backtrace = Backtrace::capture();
|
|
|
|
|
error!("Backtrace (if RUST_BACKTRACE=1):\n{}", backtrace);
|
2023-11-29 12:25:04 +01:00
|
|
|
process::exit(1);
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
fn start_program() -> Result<(DB, BCMRDownloader)> {
|
|
|
|
|
let create_db_pool = |db_path| -> (bool, DBPool, DBPool) {
|
|
|
|
|
let db_exists = Path::new(db_path).exists();
|
|
|
|
|
|
|
|
|
|
let write_manager = r2d2_sqlite::SqliteConnectionManager::file(db_path)
|
|
|
|
|
.with_flags(OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE)
|
|
|
|
|
.with_init(|c| c.execute_batch("PRAGMA foreign_keys=1;"))
|
|
|
|
|
.with_init(|c| c.busy_timeout(Duration::from_secs(30)))
|
|
|
|
|
.with_init(|c| c.execute_batch("PRAGMA journal_mode=WAL;"));
|
2024-03-14 12:14:13 +01:00
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let write_pool =
|
|
|
|
|
Arc::new(r2d2::Pool::new(write_manager).expect("Failed to initialize database"));
|
2024-03-14 15:00:38 +01:00
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let read_manager = r2d2_sqlite::SqliteConnectionManager::file(db_path)
|
|
|
|
|
.with_flags(OpenFlags::SQLITE_OPEN_READ_ONLY)
|
|
|
|
|
.with_init(|c| c.execute_batch("PRAGMA foreign_keys=1;"))
|
|
|
|
|
.with_init(|c| c.busy_timeout(Duration::from_secs(30)))
|
|
|
|
|
.with_init(|c| c.execute_batch("PRAGMA journal_mode=WAL;"));
|
|
|
|
|
|
|
|
|
|
let read_pool =
|
|
|
|
|
Arc::new(r2d2::Pool::new(read_manager).expect("Failed to initialize database"));
|
|
|
|
|
|
|
|
|
|
(db_exists, write_pool, read_pool)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (db_exists, cauldron_db_write, cauldron_db_read) = create_db_pool("cauldron.db");
|
|
|
|
|
if !db_exists {
|
|
|
|
|
db::cauldron::prepare_tables(
|
|
|
|
|
&cauldron_db_write
|
|
|
|
|
.get()
|
|
|
|
|
.expect("failed to get sqlite connection"),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let (db_exists, bcmr_db_write, bcmr_db_read) = create_db_pool("bcmr.db");
|
2023-11-29 12:25:04 +01:00
|
|
|
if !db_exists {
|
2024-05-09 11:25:27 +02:00
|
|
|
db::bcmr::prepare_tables(
|
|
|
|
|
&bcmr_db_write
|
|
|
|
|
.get()
|
|
|
|
|
.expect("failed to create sql connection"),
|
|
|
|
|
);
|
2023-11-29 12:25:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-14 11:11:16 +01:00
|
|
|
let client = Arc::new(Mutex::new(
|
|
|
|
|
Client::new("tcp://rostrum.cauldron.quest:50001").unwrap(),
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
let genesis = client
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.raw_call("blockchain.block.get", vec![Param::U32(0)])
|
|
|
|
|
.unwrap();
|
|
|
|
|
let genesis: Block = deserialize(&hex::decode(genesis.as_str().unwrap()).unwrap()).unwrap();
|
|
|
|
|
let chain = Arc::new(Mutex::new(chain::Chain::new(genesis.header)));
|
|
|
|
|
|
|
|
|
|
info!("Loading block headers...");
|
2024-05-09 11:25:27 +02:00
|
|
|
let all_headers = load_all_headers(&cauldron_db_read.get().unwrap()).unwrap();
|
2024-02-14 11:11:16 +01:00
|
|
|
info!("Initializing {} headers...", all_headers.len());
|
|
|
|
|
chain.lock().unwrap().load(all_headers).unwrap();
|
|
|
|
|
info!("Headers loaded.");
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let db = DB {
|
|
|
|
|
cauldron_w: cauldron_db_write,
|
|
|
|
|
cauldron_r: cauldron_db_read,
|
|
|
|
|
bcmr_w: bcmr_db_write,
|
|
|
|
|
bcmr_r: bcmr_db_read,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let db_cpy = db.clone();
|
|
|
|
|
// let client_cpy = client.clone();
|
2023-11-29 12:25:04 +01:00
|
|
|
|
|
|
|
|
thread::spawn(move || {
|
2024-05-09 11:25:27 +02:00
|
|
|
let db = db_cpy;
|
|
|
|
|
//let client = client_cpy;
|
2024-02-14 11:11:16 +01:00
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let mut tip: BlockHash = loop {
|
|
|
|
|
break match index_blocks(chain.clone(), db.clone(), client.clone(), true) {
|
|
|
|
|
Ok(tip) => tip,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
if e.to_string().contains("database is locked") {
|
|
|
|
|
warn!("initial index error, trying again: {}", e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
panic!("Initial index failed: {}\n {}", e, e.backtrace());
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-02-14 11:11:16 +01:00
|
|
|
};
|
2023-11-29 12:25:04 +01:00
|
|
|
loop {
|
2024-05-09 11:25:27 +02:00
|
|
|
let new_tip = match electrum_get_tip(&client.lock().unwrap()) {
|
|
|
|
|
Ok(t) => t.0.block_hash(),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("Failed to get block chain tip from electrum: {}", e);
|
|
|
|
|
thread::sleep(Duration::from_secs(5));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-29 12:25:04 +01:00
|
|
|
if new_tip != tip {
|
2024-05-09 11:25:27 +02:00
|
|
|
tip = match index_blocks(chain.clone(), db.clone(), client.clone(), true) {
|
|
|
|
|
Ok(t) => t,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
warn!("Indexing block failed: {} {}", e, e.backtrace());
|
|
|
|
|
tip
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-29 12:25:04 +01:00
|
|
|
}
|
2024-05-09 11:25:27 +02:00
|
|
|
if let Err(e) = update_mempool(db.cauldron_w.clone(), client.clone()) {
|
2024-02-16 09:11:40 +01:00
|
|
|
error!("Failed to update mempool: {}", e);
|
|
|
|
|
}
|
|
|
|
|
thread::sleep(Duration::from_secs(5));
|
2023-11-29 12:25:04 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let mut bcmrdownloader = BCMRDownloader::new(db.bcmr_w.clone());
|
|
|
|
|
bcmrdownloader.start()?;
|
|
|
|
|
|
|
|
|
|
Ok((db, bcmrdownloader))
|
2024-02-14 11:11:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[launch]
|
|
|
|
|
fn launch() -> _ {
|
|
|
|
|
stderrlog::new()
|
2024-05-09 11:25:27 +02:00
|
|
|
.verbosity(LogLevelNum::Info)
|
2024-02-14 11:11:16 +01:00
|
|
|
.init()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
set_panic_hook();
|
|
|
|
|
|
2024-05-09 11:25:27 +02:00
|
|
|
let (dbpool, bcmrdownloader) = match start_program() {
|
2024-02-14 11:11:16 +01:00
|
|
|
Ok(db) => db,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
let backtrace = Backtrace::capture();
|
|
|
|
|
error!(
|
|
|
|
|
"Backtrace (if RUST_BACKTRACE=1):\n{}",
|
|
|
|
|
backtrace.to_string()
|
|
|
|
|
);
|
|
|
|
|
error!("Error: {}", e.to_string());
|
|
|
|
|
panic!("Failed at program startup")
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-01-19 14:38:31 +01:00
|
|
|
let allowed_origins = AllowedOrigins::all();
|
|
|
|
|
|
|
|
|
|
let cors = rocket_cors::CorsOptions {
|
|
|
|
|
allowed_origins,
|
|
|
|
|
allowed_methods: vec![rocket::http::Method::Get]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(From::from)
|
|
|
|
|
.collect(),
|
|
|
|
|
allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]),
|
|
|
|
|
allow_credentials: true,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
.to_cors()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2024-05-29 08:45:50 +02:00
|
|
|
let response_cache: ResponseCache = Arc::new(Mutex::new(HashMap::default()));
|
|
|
|
|
|
2024-01-05 14:21:50 +01:00
|
|
|
rocket::build()
|
2024-03-14 12:14:13 +01:00
|
|
|
.manage(dbpool)
|
2024-05-29 08:45:50 +02:00
|
|
|
.manage(response_cache)
|
2024-05-09 11:25:27 +02:00
|
|
|
// give rocket ownership of downloader to ensure thread isn't dropped
|
|
|
|
|
.manage(bcmrdownloader)
|
2024-03-04 16:40:50 +01:00
|
|
|
.mount(
|
|
|
|
|
"/cauldron/",
|
2024-03-18 12:34:46 +01:00
|
|
|
routes![
|
2024-04-03 09:49:44 +02:00
|
|
|
rpc::tvl::deprecated_tvl,
|
|
|
|
|
rpc::tvl::valuelocked_token,
|
|
|
|
|
rpc::tvl::valuelocked_all,
|
2024-05-09 11:25:27 +02:00
|
|
|
rpc::tokens::list_by_volume,
|
2024-04-03 11:39:49 +02:00
|
|
|
rpc::price::price_history,
|
|
|
|
|
rpc::price::price_current,
|
2024-04-03 21:40:33 +02:00
|
|
|
rpc::pool::list_pools_by_apy,
|
|
|
|
|
rpc::pool::list_active_pools,
|
2024-04-03 11:39:49 +02:00
|
|
|
rpc::contract::contract_count_token,
|
|
|
|
|
rpc::contract::contract_count_all,
|
2024-05-09 11:25:27 +02:00
|
|
|
rpc::contract::contract_volume
|
2024-03-18 12:34:46 +01:00
|
|
|
],
|
2024-03-04 16:40:50 +01:00
|
|
|
)
|
2024-05-09 11:25:27 +02:00
|
|
|
.mount("/bcmr", routes![rpc::bcmr::token_bcmr,])
|
2024-01-19 14:38:31 +01:00
|
|
|
.attach(cors)
|
2023-11-29 12:25:04 +01:00
|
|
|
}
|