Merge branch 'crc20-index' into 'master'
Add CRC20 index See merge request riftenlabs/riftenlabs-indexer!16
This commit is contained in:
commit
d2b169d03a
9 changed files with 443 additions and 56 deletions
|
|
@ -19,7 +19,10 @@ use riftenlabs_defi::chainutil::{compute_outpoint_hash, read_push_from_script, O
|
|||
use rusqlite::Connection;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::db::bcmr::{get_matching_autheaders, insert_authheader, AuthChainEntry};
|
||||
use crate::{
|
||||
db::bcmr::{get_matching_autheaders, insert_authheader, AuthChainEntry},
|
||||
utiltoken::is_genesis_tx,
|
||||
};
|
||||
|
||||
pub mod bcmrdownloader;
|
||||
pub mod parsedbcmr;
|
||||
|
|
@ -92,27 +95,6 @@ pub fn parse_bcmr(tx: &Transaction) -> Option<BCMR> {
|
|||
parse_bcmr_from_opreturn(&bcmr_op_return.script_pubkey)
|
||||
}
|
||||
|
||||
fn is_genesis_tx(tx: &Transaction) -> Option<TokenID> {
|
||||
let potential_token_ids: HashSet<[u8; 32]> = tx
|
||||
.input
|
||||
.iter()
|
||||
.filter(|i| i.previous_output.vout == 0)
|
||||
.map(|i| i.previous_output.txid.into_inner())
|
||||
.collect();
|
||||
|
||||
for o in &tx.output {
|
||||
match &o.token {
|
||||
Some(token) => {
|
||||
if potential_token_ids.contains(token.id.as_inner()) {
|
||||
return Some(token.id);
|
||||
}
|
||||
}
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn ttor_sorted(txs: Vec<Transaction>) -> Vec<Transaction> {
|
||||
let txs = {
|
||||
let mut queue: VecDeque<Transaction> = txs.into_iter().collect();
|
||||
|
|
@ -325,16 +307,4 @@ mod tests {
|
|||
// The hash value in this BCMR is 64 bytes. Not a valid sha256 hash.
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_genesis_tx() {
|
||||
let tx_hex = "01000000032b70d6279de034fea76d4dd329c44a3e162b17a0cebc32eeeebbd14f5e8a97250100000064411b42a9dcb22c59ff0e800b3c63d6b8ac045bd18eb1effc8d2f23ec49ed0abdc316215a469a206d45153eac999d14eb8abc3d33b91c5ac4b61e7868d162d5d5184121021735db91ac477c1007da74c8c117c383eebc664d21f12871ca6e69401f8072f8feffffff2b70d6279de034fea76d4dd329c44a3e162b17a0cebc32eeeebbd14f5e8a972502000000644193e4d5bb533279fbcbebe15ba62f226e53e5c64a1624c755b255b13f0b1011e382182896026575370b5e30d03390d31faabf854a9f41c215b8a5bb57bae221df4121021735db91ac477c1007da74c8c117c383eebc664d21f12871ca6e69401f8072f8feffffff8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb12280100000064418b576b5defc8942182d01654cc2a0706b3e86e9031f7a13a694ffd830944ca00ecf5ad3271a242993580cbe78c13a41e35b06dfd68e72c4242dd7ff9e7e039a6412103b666cf8bc6ef2e3c3f100fc883013040f01f7085d75e5ab0811ae00b6a0a6d24feffffff03200300000000000040ef8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb1228600401db010076a914bfe9ed4ea9c7830c2ee96c17a3bd5ff563c12f2b88ac200300000000000040ef8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb1228600402db010076a914bfe9ed4ea9c7830c2ee96c17a3bd5ff563c12f2b88ac9f117901000000001976a91402a67e8d884367e7a22d6cda96a6df21e56a2f3288ac851b0c00";
|
||||
let not_genesis_tx: Transaction = deserialize(&hex::decode(&tx_hex).unwrap()).unwrap();
|
||||
|
||||
let tx_hex = "0200000001925bd92e424c0f0bc290a794f491abf19e61b6dcdec7e70747fcb54682fc9bb700000000644181707613f45069c8df981b2ef0cbd05158d5623602fac3ae2e13f69d05fa356c278f2f3a4e34f33a79e0f8375abd35051150ec024b3625807cea185528a5de9e412103b4680dffa1e34b3bfdb024fd0a8498eb24f59ba5ead34acfb74f9d8549db6f3a0000000003e8030000000000003eef925bd92e424c0f0bc290a794f491abf19e61b6dcdec7e70747fcb54682fc9bb710fdf40176a914bd4c90f2c64743fc0d3ea6a14973a5d628260b7388ac0000000000000000456a0442434d5220c705cc90a56ac7ef9a15ef90ebbc8ba7e60e4c622e5464d52d8baf7887949fcc1d736f636b2e6361756c64726f6e2e71756573742f62636d722e6a736f6ed9210000000000001976a914bd4c90f2c64743fc0d3ea6a14973a5d628260b7388ac00000000";
|
||||
let genesis_tx: Transaction = deserialize(&hex::decode(&tx_hex).unwrap()).unwrap();
|
||||
|
||||
assert!(!is_genesis_tx(¬_genesis_tx).is_some());
|
||||
assert!(is_genesis_tx(&genesis_tx).is_some());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
173
src/crc20/crc20fetcher.rs
Normal file
173
src/crc20/crc20fetcher.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// 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 std::{
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::db::crc20::{
|
||||
bump_failed_attempts, get_not_indexed_tokens, update_to_crc20, update_to_not_crc20,
|
||||
};
|
||||
use crate::db::DBPool;
|
||||
use anyhow::*;
|
||||
use electrum_client_netagnostic::{Client, ElectrumApi, Param};
|
||||
use log::{info, warn};
|
||||
use rand::thread_rng;
|
||||
use serde_json::Value;
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use std::result::Result::Ok;
|
||||
|
||||
pub struct CRC20Fetcher {
|
||||
keep_running: Arc<AtomicBool>,
|
||||
update_thread: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
const LOOP_SLEEP_TIME: Duration = Duration::from_secs(10);
|
||||
|
||||
fn parse_crc20_info(json: &Value) -> Option<(String, String, i32)> {
|
||||
let crc20 = json.get("crc20");
|
||||
if crc20.is_none() || crc20 == Some(&Value::Null) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let symbol = crc20
|
||||
.and_then(|c| c.get("symbol"))
|
||||
.and_then(|s| s.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let name = crc20
|
||||
.and_then(|c| c.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.map(|n| n.to_string());
|
||||
|
||||
let decimals = crc20
|
||||
.and_then(|c| c.get("decimals"))
|
||||
.and_then(|d| d.as_i64())
|
||||
.map(|d| d as i32);
|
||||
|
||||
Some((
|
||||
symbol.unwrap_or_default(),
|
||||
name.unwrap_or_default(),
|
||||
decimals.unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
|
||||
impl CRC20Fetcher {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
keep_running: Arc::new(AtomicBool::new(true)),
|
||||
update_thread: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, db: DBPool, electrum: Arc<Mutex<Client>>) -> Result<()> {
|
||||
let keep_running_cpy = self.keep_running.clone();
|
||||
self.update_thread = Some(
|
||||
thread::Builder::new()
|
||||
.name("crc20_fetcher".to_string())
|
||||
.spawn(move || loop {
|
||||
if !keep_running_cpy.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
info!("Exiting crc20 fetcher thread");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut queue = {
|
||||
let db = match db.get() {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
warn!("Failed to get crc20 db connection {}", e);
|
||||
thread::sleep(LOOP_SLEEP_TIME);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match get_not_indexed_tokens(&db) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(e) => {
|
||||
warn!("Failed to get unindexed crc20 tokens {}", e);
|
||||
thread::sleep(LOOP_SLEEP_TIME);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// in case electrum has issues with a token; with rng we'll eventually get all others
|
||||
let mut rng = thread_rng();
|
||||
queue.shuffle(&mut rng);
|
||||
|
||||
if queue.is_empty() {
|
||||
thread::sleep(LOOP_SLEEP_TIME);
|
||||
continue;
|
||||
}
|
||||
|
||||
info!("crc20: {} tokens need fetching", queue.len());
|
||||
|
||||
for token in queue {
|
||||
let genesis_info = match electrum
|
||||
.lock()
|
||||
.unwrap()
|
||||
.raw_call("token.genesis.info", vec![Param::String(token.clone())])
|
||||
{
|
||||
Ok(i) => Some(i),
|
||||
Err(e) => {
|
||||
info!("Failed to fetch crc20 info for {}: {}", token, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let db = match db.get() {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
warn!("Failed to get crc20 db connection {}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if genesis_info.is_none() {
|
||||
if let Err(e) = bump_failed_attempts(&db, &token) {
|
||||
warn!("Failed to bump failed attempts for {}: {}", token, e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let res = if let Some((symbol, name, decimals)) =
|
||||
parse_crc20_info(&genesis_info.unwrap())
|
||||
{
|
||||
update_to_crc20(&db, &token, &symbol, &name, decimals)
|
||||
} else {
|
||||
update_to_not_crc20(&db, &token)
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
warn!("Failed to update crc20 for token {}: {}", token, e);
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(LOOP_SLEEP_TIME)
|
||||
})
|
||||
.expect("failed to start bcmr download thread"),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CRC20Fetcher {
|
||||
fn drop(&mut self) {
|
||||
self.keep_running
|
||||
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
if let Some(thread) = self.update_thread.take() {
|
||||
// Wake the thread in case it is sleeping
|
||||
thread.thread().unpark();
|
||||
|
||||
match thread.join() {
|
||||
Ok(_) => info!("crc20 fetcher thread done"),
|
||||
Err(e) => warn!("Failed to join crc20 fetcher thread: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/crc20/mod.rs
Normal file
23
src/crc20/mod.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// 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 bitcoincash::{TokenID, Transaction};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use rusqlite::Connection;
|
||||
|
||||
pub mod crc20fetcher;
|
||||
|
||||
use crate::{db::crc20::insert_crc20_candidate, utiltoken::is_genesis_tx};
|
||||
|
||||
pub fn index_crc20(conn: &Connection, txs: &Vec<Transaction>) -> Result<()> {
|
||||
let token_genesis: Vec<TokenID> = txs.par_iter().filter_map(is_genesis_tx).collect();
|
||||
|
||||
for token_id in token_genesis {
|
||||
insert_crc20_candidate(conn, &token_id)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
137
src/db/crc20/mod.rs
Normal file
137
src/db/crc20/mod.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// 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::ToHex;
|
||||
use bitcoincash::TokenID;
|
||||
use rusqlite::{params, Connection};
|
||||
|
||||
const STATE_NOT_INDEXED: i32 = -1;
|
||||
const STATE_NOT_CRC20: i32 = 0;
|
||||
const STATE_IS_CRC20: i32 = 1;
|
||||
|
||||
const MAX_FAILED_ATTEMPTS: i32 = 20;
|
||||
|
||||
pub fn prepare_tables(conn: &Connection) {
|
||||
conn.execute(
|
||||
"CREATE TABLE crc20 (
|
||||
token_id TEXT PRIMARY KEY,
|
||||
symbol TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
decimals INT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.expect("failed to create crc20 table");
|
||||
|
||||
conn.execute(
|
||||
"CREATE TABLE crc20_candidates (
|
||||
token_id TEXT PRIMARY KEY,
|
||||
is_crc20 INT NOT NULL,
|
||||
failed_attempts INT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.expect("failed to create crc20 table");
|
||||
}
|
||||
|
||||
pub fn insert_crc20_candidate(conn: &Connection, token_id: &TokenID) -> Result<()> {
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO crc20_candidates (token_id, is_crc20, failed_attempts)
|
||||
VALUES (?1, ?2, ?3)",
|
||||
params![token_id.to_hex(), STATE_NOT_INDEXED, 0],
|
||||
)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to insert crc20 candidate: token_id = {}. Original error: {:?}",
|
||||
token_id.to_hex(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_not_indexed_tokens(conn: &Connection) -> Result<Vec<String>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT token_id FROM crc20_candidates WHERE is_crc20 = ?1 AND failed_attempts <= ?2",
|
||||
)?;
|
||||
let token_ids = stmt
|
||||
.query_map(params![STATE_NOT_INDEXED, MAX_FAILED_ATTEMPTS], |row| {
|
||||
row.get(0)
|
||||
})?
|
||||
.collect::<Result<Vec<String>, _>>()?;
|
||||
|
||||
Ok(token_ids)
|
||||
}
|
||||
|
||||
pub fn update_to_not_crc20(conn: &Connection, token: &str) -> Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE crc20_candidates SET is_crc20 = ?1 WHERE token_id = ?2",
|
||||
params![STATE_NOT_CRC20, token],
|
||||
)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to update token_id = {} to STATE_NOT_CRC20. Original error: {:?}",
|
||||
token,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_to_crc20(
|
||||
conn: &Connection,
|
||||
token_id: &str,
|
||||
symbol: &str,
|
||||
name: &str,
|
||||
decimals: i32,
|
||||
) -> Result<()> {
|
||||
// Update is_crc20 in crc20_candidates table
|
||||
conn.execute(
|
||||
"UPDATE crc20_candidates SET is_crc20 = ?1 WHERE token_id = ?2",
|
||||
params![STATE_IS_CRC20, token_id],
|
||||
)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to update token_id = {} to STATE_IS_CRC20 in crc20_candidates. Original error: {:?}",
|
||||
token_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
// Insert or replace the token in the crc20 table
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO crc20 (token_id, symbol, name, decimals)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![token_id, symbol, name, decimals],
|
||||
)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to insert or replace token_id = {} in crc20. Original error: {:?}",
|
||||
token_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bump_failed_attempts(conn: &Connection, token_id: &str) -> Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE crc20_candidates SET failed_attempts = failed_attempts + 1 WHERE token_id = ?1",
|
||||
params![token_id],
|
||||
)
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to bump failed_attempts for token_id = {}. Original error: {:?}",
|
||||
token_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use std::sync::Arc;
|
|||
|
||||
pub mod bcmr;
|
||||
pub mod cauldron;
|
||||
pub mod crc20;
|
||||
|
||||
pub type DBPool = Arc<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>;
|
||||
|
||||
|
|
@ -20,4 +21,9 @@ pub struct DB {
|
|||
pub bcmr_r: DBPool,
|
||||
// readwrite
|
||||
pub bcmr_w: DBPool,
|
||||
// rewadwrite
|
||||
pub crc20_w: DBPool,
|
||||
// readonly
|
||||
#[allow(dead_code)]
|
||||
pub crc20_r: DBPool,
|
||||
}
|
||||
|
|
|
|||
11
src/index.rs
11
src/index.rs
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
|
||||
use bitcoin_hashes::hex::{FromHex, ToHex};
|
||||
use bitcoincash::{consensus::deserialize, Block, BlockHash, Transaction, Txid};
|
||||
use electrum_client_netagnostic::{Client, Param, ElectrumApi};
|
||||
use electrum_client_netagnostic::{Client, ElectrumApi, Param};
|
||||
use log::{debug, info, warn};
|
||||
use rayon::prelude::*;
|
||||
use riftenlabs_defi::cauldron::{parse_cauldron, ParsedContract};
|
||||
|
|
@ -19,6 +19,7 @@ use riftenlabs_defi::cauldron::{parse_cauldron, ParsedContract};
|
|||
use crate::{
|
||||
bcmr::index_bcmr,
|
||||
chain::{get_new_headers, Chain, StoreBlockUndoer},
|
||||
crc20::index_crc20,
|
||||
db::{
|
||||
self,
|
||||
cauldron::{
|
||||
|
|
@ -245,6 +246,14 @@ pub fn index_blocks(
|
|||
.context("update pool history")?;
|
||||
config_set(&db_tx, KEY_LAST_INDEXED, &blockhash.to_hex());
|
||||
|
||||
{
|
||||
// crc20
|
||||
let mut conn = db.crc20_w.get().context("failed to get crc20 db")?;
|
||||
let tx = conn.transaction()?;
|
||||
index_crc20(&tx, &block.txdata)?;
|
||||
tx.commit()?;
|
||||
}
|
||||
|
||||
let autheader_updates = if bcmr_enabled {
|
||||
let mut conn = db.bcmr_w.get().context("failed to get bcmr db")?;
|
||||
let bcmr_db_tx = conn.transaction()?;
|
||||
|
|
|
|||
28
src/main.rs
28
src/main.rs
|
|
@ -6,6 +6,7 @@
|
|||
use anyhow::Result;
|
||||
use bcmr::wellknowndowloader::WellKnownDownloader;
|
||||
use bitcoincash::{consensus::deserialize, Block, BlockHash};
|
||||
use crc20::crc20fetcher::CRC20Fetcher;
|
||||
use db::{DBPool, DB};
|
||||
use electrum::electrum_get_tip;
|
||||
use electrum_client_netagnostic::{Client, ElectrumApi, Param};
|
||||
|
|
@ -45,11 +46,13 @@ const KEY_LAST_INDEXED: &str = "last_indexed";
|
|||
mod bcmr;
|
||||
mod cashaddr;
|
||||
mod chain;
|
||||
mod crc20;
|
||||
mod db;
|
||||
mod electrum;
|
||||
mod index;
|
||||
mod rpc;
|
||||
mod timeutil;
|
||||
mod utiltoken;
|
||||
|
||||
fn set_panic_hook() {
|
||||
panic::set_hook(Box::new(|panic_info| {
|
||||
|
|
@ -64,6 +67,8 @@ fn set_panic_hook() {
|
|||
}
|
||||
} else if let Some(message) = panic_info.payload().downcast_ref::<&str>() {
|
||||
error!("Panic occurred: {}", message);
|
||||
} else if let Some(message) = panic_info.payload().downcast_ref::<String>() {
|
||||
error!("Panic occurred: {}", message);
|
||||
} else {
|
||||
error!("Panic info: {:?}", panic_info);
|
||||
}
|
||||
|
|
@ -74,7 +79,7 @@ fn set_panic_hook() {
|
|||
}));
|
||||
}
|
||||
|
||||
fn start_program() -> Result<(DB, BCMRDownloader, WellKnownDownloader)> {
|
||||
fn start_program() -> Result<(DB, BCMRDownloader, WellKnownDownloader, CRC20Fetcher)> {
|
||||
let create_db_pool = |db_path| -> (bool, DBPool, DBPool) {
|
||||
let db_exists = Path::new(db_path).exists();
|
||||
|
||||
|
|
@ -116,6 +121,15 @@ fn start_program() -> Result<(DB, BCMRDownloader, WellKnownDownloader)> {
|
|||
);
|
||||
}
|
||||
|
||||
let (db_exists, crc20_db_write, crc20_db_read) = create_db_pool("crc20.db");
|
||||
if !db_exists {
|
||||
db::crc20::prepare_tables(
|
||||
&crc20_db_write
|
||||
.get()
|
||||
.expect("failed to create sqlite crc20 connection"),
|
||||
);
|
||||
}
|
||||
|
||||
let client = Arc::new(Mutex::new(
|
||||
Client::new("tcp://rostrum.cauldron.quest:50001").unwrap(),
|
||||
));
|
||||
|
|
@ -139,14 +153,17 @@ fn start_program() -> Result<(DB, BCMRDownloader, WellKnownDownloader)> {
|
|||
cauldron_r: cauldron_db_read,
|
||||
bcmr_w: bcmr_db_write,
|
||||
bcmr_r: bcmr_db_read,
|
||||
crc20_w: crc20_db_write,
|
||||
crc20_r: crc20_db_read,
|
||||
};
|
||||
|
||||
let db_cpy = db.clone();
|
||||
// let client_cpy = client.clone();
|
||||
|
||||
let mut crc20fetcher = CRC20Fetcher::new();
|
||||
crc20fetcher.start(db.crc20_w.clone(), client.clone())?;
|
||||
|
||||
thread::spawn(move || {
|
||||
let db = db_cpy;
|
||||
//let client = client_cpy;
|
||||
|
||||
let mut tip: BlockHash = loop {
|
||||
break match index_blocks(chain.clone(), db.clone(), client.clone(), true) {
|
||||
|
|
@ -192,7 +209,7 @@ fn start_program() -> Result<(DB, BCMRDownloader, WellKnownDownloader)> {
|
|||
let mut wellknowndownloader = WellKnownDownloader::new(db.bcmr_w.clone());
|
||||
wellknowndownloader.start()?;
|
||||
|
||||
Ok((db, bcmrdownloader, wellknowndownloader))
|
||||
Ok((db, bcmrdownloader, wellknowndownloader, crc20fetcher))
|
||||
}
|
||||
|
||||
#[launch]
|
||||
|
|
@ -204,7 +221,7 @@ fn launch() -> _ {
|
|||
|
||||
set_panic_hook();
|
||||
|
||||
let (dbpool, bcmrdownloader, wellknowndownloader) = match start_program() {
|
||||
let (dbpool, bcmrdownloader, wellknowndownloader, crc20fetcher) = match start_program() {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
let backtrace = Backtrace::capture();
|
||||
|
|
@ -239,6 +256,7 @@ fn launch() -> _ {
|
|||
// give rocket ownership of downloader to ensure thread isn't dropped
|
||||
.manage(bcmrdownloader)
|
||||
.manage(wellknowndownloader)
|
||||
.manage(crc20fetcher)
|
||||
.mount(
|
||||
"/cauldron/",
|
||||
routes![
|
||||
|
|
|
|||
|
|
@ -191,33 +191,33 @@ fn price_at_or_before(
|
|||
) -> Result<(i64, f64)> {
|
||||
let sql = "
|
||||
WITH max_timestamps AS (
|
||||
SELECT
|
||||
SELECT
|
||||
phe.pool,
|
||||
MAX(COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp)) AS max_effective_timestamp
|
||||
FROM
|
||||
FROM
|
||||
pool_history_entry phe
|
||||
JOIN
|
||||
JOIN
|
||||
utxo_funding uf ON phe.utxo = uf.new_utxo_hash
|
||||
WHERE
|
||||
WHERE
|
||||
COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) <= ?
|
||||
AND uf.token_id = ?
|
||||
GROUP BY
|
||||
GROUP BY
|
||||
phe.pool
|
||||
)
|
||||
SELECT
|
||||
uf.token_amount,
|
||||
uf.sats,
|
||||
COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) AS effective_timestamp
|
||||
FROM
|
||||
FROM
|
||||
pool_history_entry phe
|
||||
JOIN
|
||||
JOIN
|
||||
utxo_funding uf ON phe.utxo = uf.new_utxo_hash
|
||||
JOIN
|
||||
JOIN
|
||||
pool p ON p.creation_utxo = phe.pool
|
||||
JOIN
|
||||
JOIN
|
||||
max_timestamps mt ON phe.pool = mt.pool
|
||||
AND COALESCE(phe.first_seen_timestamp, phe.mtp_timestamp) = mt.max_effective_timestamp
|
||||
WHERE
|
||||
WHERE
|
||||
p.withdrawn_in_utxo IS NULL
|
||||
";
|
||||
|
||||
|
|
@ -503,6 +503,8 @@ mod tests {
|
|||
cauldron_r: Arc::new(pool.clone()),
|
||||
bcmr_w: Arc::new(pool.clone()),
|
||||
bcmr_r: Arc::new(pool.clone()),
|
||||
crc20_w: Arc::new(pool.clone()),
|
||||
crc20_r: Arc::new(pool.clone()),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
|
|
@ -760,8 +762,8 @@ mod tests {
|
|||
.execute(
|
||||
"INSERT INTO utxo_funding (new_utxo_hash, txid, sats, token_amount, token_id) VALUES (?, ?, ?, ?, ?)",
|
||||
params![
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
1_i64, // Low sats
|
||||
9999999999999999_i64, // Very high token amount
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
|
@ -773,10 +775,10 @@ mod tests {
|
|||
.execute(
|
||||
"INSERT INTO pool_history_entry (pool, utxo, txid, tx_pos, mtp_timestamp, first_seen_timestamp) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
params![
|
||||
"pool_high_tokens",
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
"tx_pos_high_tokens",
|
||||
"pool_high_tokens",
|
||||
"mock_utxo_hash_high_tokens",
|
||||
"test_txid_high_tokens",
|
||||
"tx_pos_high_tokens",
|
||||
1727963300,
|
||||
1727963300
|
||||
],
|
||||
|
|
|
|||
49
src/utiltoken.rs
Normal file
49
src/utiltoken.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// 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 std::collections::HashSet;
|
||||
|
||||
use bitcoin_hashes::Hash;
|
||||
use bitcoincash::{TokenID, Transaction};
|
||||
|
||||
pub fn is_genesis_tx(tx: &Transaction) -> Option<TokenID> {
|
||||
let potential_token_ids: HashSet<[u8; 32]> = tx
|
||||
.input
|
||||
.iter()
|
||||
.filter(|i| i.previous_output.vout == 0)
|
||||
.map(|i| i.previous_output.txid.into_inner())
|
||||
.collect();
|
||||
|
||||
for o in &tx.output {
|
||||
match &o.token {
|
||||
Some(token) => {
|
||||
if potential_token_ids.contains(token.id.as_inner()) {
|
||||
return Some(token.id);
|
||||
}
|
||||
}
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bitcoincash::consensus::deserialize;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_genesis_tx() {
|
||||
let tx_hex = "01000000032b70d6279de034fea76d4dd329c44a3e162b17a0cebc32eeeebbd14f5e8a97250100000064411b42a9dcb22c59ff0e800b3c63d6b8ac045bd18eb1effc8d2f23ec49ed0abdc316215a469a206d45153eac999d14eb8abc3d33b91c5ac4b61e7868d162d5d5184121021735db91ac477c1007da74c8c117c383eebc664d21f12871ca6e69401f8072f8feffffff2b70d6279de034fea76d4dd329c44a3e162b17a0cebc32eeeebbd14f5e8a972502000000644193e4d5bb533279fbcbebe15ba62f226e53e5c64a1624c755b255b13f0b1011e382182896026575370b5e30d03390d31faabf854a9f41c215b8a5bb57bae221df4121021735db91ac477c1007da74c8c117c383eebc664d21f12871ca6e69401f8072f8feffffff8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb12280100000064418b576b5defc8942182d01654cc2a0706b3e86e9031f7a13a694ffd830944ca00ecf5ad3271a242993580cbe78c13a41e35b06dfd68e72c4242dd7ff9e7e039a6412103b666cf8bc6ef2e3c3f100fc883013040f01f7085d75e5ab0811ae00b6a0a6d24feffffff03200300000000000040ef8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb1228600401db010076a914bfe9ed4ea9c7830c2ee96c17a3bd5ff563c12f2b88ac200300000000000040ef8d80b5567b6a3cac6e19a6934fd7544a02616fd6868a304c93bff6aed3eb1228600402db010076a914bfe9ed4ea9c7830c2ee96c17a3bd5ff563c12f2b88ac9f117901000000001976a91402a67e8d884367e7a22d6cda96a6df21e56a2f3288ac851b0c00";
|
||||
let not_genesis_tx: Transaction = deserialize(&hex::decode(&tx_hex).unwrap()).unwrap();
|
||||
|
||||
let tx_hex = "0200000001925bd92e424c0f0bc290a794f491abf19e61b6dcdec7e70747fcb54682fc9bb700000000644181707613f45069c8df981b2ef0cbd05158d5623602fac3ae2e13f69d05fa356c278f2f3a4e34f33a79e0f8375abd35051150ec024b3625807cea185528a5de9e412103b4680dffa1e34b3bfdb024fd0a8498eb24f59ba5ead34acfb74f9d8549db6f3a0000000003e8030000000000003eef925bd92e424c0f0bc290a794f491abf19e61b6dcdec7e70747fcb54682fc9bb710fdf40176a914bd4c90f2c64743fc0d3ea6a14973a5d628260b7388ac0000000000000000456a0442434d5220c705cc90a56ac7ef9a15ef90ebbc8ba7e60e4c622e5464d52d8baf7887949fcc1d736f636b2e6361756c64726f6e2e71756573742f62636d722e6a736f6ed9210000000000001976a914bd4c90f2c64743fc0d3ea6a14973a5d628260b7388ac00000000";
|
||||
let genesis_tx: Transaction = deserialize(&hex::decode(&tx_hex).unwrap()).unwrap();
|
||||
|
||||
assert!(!is_genesis_tx(¬_genesis_tx).is_some());
|
||||
assert!(is_genesis_tx(&genesis_tx).is_some());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue