diff --git a/src/bin/bal-pusher.rs b/src/bin/bal-pusher.rs index 65edd14..f41728c 100644 --- a/src/bin/bal-pusher.rs +++ b/src/bin/bal-pusher.rs @@ -11,7 +11,7 @@ use log::{debug, error, info, trace, warn}; use serde::Deserialize; use serde::Serialize; use serde_json::json; -use sqlite::{Value, Connection}; +use sqlite::{Connection, Value}; use std::collections::HashMap; use std::env; use std::error::Error as StdError; @@ -48,14 +48,18 @@ struct MyConfig { impl Default for MyConfig { fn default() -> Self { MyConfig { - zmq_listener: env::var("BAL_PUSHER_ZMQ_LISTENER").unwrap_or("tcp://127.0.0.1:28332".to_string()), + zmq_listener: env::var("BAL_PUSHER_ZMQ_LISTENER") + .unwrap_or("tcp://127.0.0.1:28332".to_string()), db_file: env::var("BAL_PUSHER_DB_FILE").unwrap_or("bal.db".to_string()), bitcoin_dir: env::var("BAL_PUSHER_BITCOIN_DIR").unwrap_or("".to_string()), regtest: get_network_params_default(Network::Regtest), testnet: get_network_params_default(Network::Testnet), signet: get_network_params_default(Network::Signet), mainnet: get_network_params_default(Network::Bitcoin), - send_stats: env::var("BAL_PUSHER_SEND_STATS").unwrap_or("false".to_string()).parse::().unwrap(), + send_stats: env::var("BAL_PUSHER_SEND_STATS") + .unwrap_or("false".to_string()) + .parse::() + .unwrap(), url: env::var("BAL_SERVER_URL").unwrap_or("http://localhost/".to_string()), ssl_key_path: env::var("SSL_KEY_PATH").unwrap_or("privkey.pem".to_string()), } @@ -216,7 +220,7 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<( let average_time = bcinfo.median_time; let db = sqlite::open(&cfg.db_file).unwrap(); - info!("db open {}",&cfg.db_file); + info!("db open {}", &cfg.db_file); let sqlquery = "SELECT * FROM tbl_tx WHERE network = :network AND status = :status AND ( locktime < :bestblock_height OR locktime > :locktime_threshold AND locktime < :bestblock_time);"; let query_tx = db.prepare(sqlquery).unwrap().into_iter(); @@ -294,7 +298,7 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<( } } let _ = send_stats_report(cfg, bcinfo).await; - let _ = calculate_stats(&db,network_params.db_field.clone()).await; + let _ = calculate_stats(&db, network_params.db_field.clone()).await; } Err(erx) => { panic!("impossible to get client {}", erx) @@ -302,13 +306,14 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<( } Ok(()) } -async fn calculate_stats(db: &Connection,chain: String) -> Result<(), reqwest::Error> { +async fn calculate_stats(db: &Connection, chain: String) -> Result<(), reqwest::Error> { //let sql = "drop table if exists tbl_stats;"; let sql = "DELETE FROM tbl_stats WHERE chain = '{chain}';"; - if let Err(err) = db.execute(&sql){ + if let Err(err) = db.execute(&sql) { error!("error deleting from tbl_stats where chain:{chain} error: {err}"); } - let sql = format!("INSERT INTO tbl_stats ( + let sql = format!( + "INSERT INTO tbl_stats ( report_date, chain, totals, waiting, sent, failed, waiting_profit, sent_profit, missed_profit, unique_inputs ) @@ -322,7 +327,7 @@ VALUES ( (SELECT IFNULL(SUM(our_fees),0) FROM tbl_tx WHERE status = 0 AND network = '{chain}'), (SELECT IFNULL(SUM(our_fees),0) FROM tbl_tx WHERE status = 1 AND network = '{chain}'), (SELECT IFNULL(SUM(our_fees),0) FROM tbl_tx WHERE status = 2 AND network = '{chain}'), - (SELECT COUNT(DISTINCT tbl_inp.in_txid) -- or appropriate input identifier + (SELECT COUNT(DISTINCT tbl_inp.in_txid) FROM tbl_inp JOIN tbl_tx ON tbl_inp.txid = tbl_tx.txid WHERE tbl_tx.status = 0 AND tbl_tx.network = '{chain}') @@ -337,10 +342,11 @@ ON CONFLICT(chain) DO UPDATE SET sent_profit = excluded.sent_profit, missed_profit = excluded.missed_profit, unique_inputs = excluded.unique_inputs; - "); + " + ); /* - let sql = format!("CREATE TABLE tbl_stats AS + let sql = format!("CREATE TABLE tbl_stats AS SELECT CURRENT_TIMESTAMP AS report_date, '{chain}' as chain, @@ -364,10 +370,9 @@ ON CONFLICT(chain) DO UPDATE SET unique_inputs = (SELECT COUNT(*) FROM tbl_inp JOIN tbl_tx ON(tbl_inp.txid = tbl_tx.txid) WHERE tbl_tx.status=0 AND tbl_tx.network ='{chain}') WHERE chain = '{chain}' */ - if let Err(err) = db.execute(&sql){ + if let Err(err) = db.execute(&sql) { error!("error inserting creating stats table {err}"); - } - else{ + } else { info!("tbl_stats creation success"); } Ok(()) @@ -495,7 +500,6 @@ fn parse_env_netconfig(cfg_lock: &mut MyConfig, chain: &str) -> NetworkParams { cfg.clone() } - fn check_zmq_connection(endpoint: &str) -> bool { trace!("check zmq connection"); let context = Context::new(); diff --git a/src/bin/bal-server.rs b/src/bin/bal-server.rs index 658f6c7..bbdb846 100644 --- a/src/bin/bal-server.rs +++ b/src/bin/bal-server.rs @@ -71,7 +71,7 @@ struct MyConfig { } #[derive(Debug, Serialize, Deserialize)] -pub struct InfoResponse{ +pub struct InfoResponse { pub address: String, pub base_fee: u64, pub chain: String, @@ -82,14 +82,14 @@ pub struct InfoResponse{ pub struct StatsResponse { pub report_date: String, pub chain: String, - pub totals:i64, - pub waiting:i64, - pub sent:i64, - pub failed:i64, - pub waiting_profit:i64, - pub sent_profit:i64, - pub missed_profit:i64, - pub unique_inputs:i64, + pub totals: i64, + pub waiting: i64, + pub sent: i64, + pub failed: i64, + pub waiting_profit: i64, + pub sent_profit: i64, + pub missed_profit: i64, + pub unique_inputs: i64, } impl Default for MyConfig { @@ -104,7 +104,10 @@ impl Default for MyConfig { db_file: "bal.db".to_string(), info: "Will Executor Server".to_string(), pub_key_path: "public_key.pem".to_string(), - expose_stats:env::var("BAL_SERVER_EXPOSE_STATS").unwrap_or("false".to_string()).parse::().unwrap(), + expose_stats: env::var("BAL_SERVER_EXPOSE_STATS") + .unwrap_or("false".to_string()) + .parse::() + .unwrap(), } } } @@ -136,15 +139,15 @@ async fn echo_pub_key( async fn echo_stats( param: &str, cfg: &MyConfig, - remote_addr: &String, ) -> Result>, hyper::Error> { - info!("echo stats!!! {} - {}", param,cfg.expose_stats); + info!("echo stats!!! {} - {}", param, cfg.expose_stats); let netconfig = MyConfig::get_net_config(cfg, param); if !netconfig.enabled { debug!("network disabled {}", param); return Ok(Response::new(full("network disabled"))); } - let sql = format!("SELECT + let sql = format!( + "SELECT report_date, chain, totals, @@ -155,26 +158,43 @@ async fn echo_stats( sent_profit, missed_profit, unique_inputs FROM tbl_stats where chain = '{}' - ", netconfig.name); - let mut stats:Vec=vec![]; + ", + netconfig.name + ); + let mut stats: Vec = vec![]; let db = sqlite::open(&cfg.db_file).unwrap(); - db.iterate(&sql,|pairs|{ - let row: HashMap<_, _> = pairs.into_iter().map(|(k,v)| (k.to_string(), v.map(|s| s))).collect(); + let _ = db.iterate(&sql, |pairs| { + let row: HashMap<_, _> = pairs + .into_iter() + .map(|(k, v)| (k.to_string(), v.map(|s| s))) + .collect(); //let row:HashMap<_,_>= pairs.into_iter().collect(); - println!("row report date {}",row["report_date"].clone().unwrap()); - + println!("row report date {}", row["report_date"].clone().unwrap()); + dbg!(&row); - stats.push(StatsResponse{ + stats.push(StatsResponse { report_date: row["report_date"].clone().unwrap().to_string(), chain: row["chain"].clone().unwrap().to_string(), totals: row["totals"].clone().unwrap().parse::().unwrap(), waiting: row["waiting"].clone().unwrap().parse::().unwrap(), sent: row["sent"].clone().unwrap().parse::().unwrap(), failed: row["failed"].clone().unwrap().parse::().unwrap(), - waiting_profit: row["waiting_profit"].clone().unwrap().parse::().unwrap(), + waiting_profit: row["waiting_profit"] + .clone() + .unwrap() + .parse::() + .unwrap(), sent_profit: row["sent_profit"].clone().unwrap().parse::().unwrap(), - missed_profit:row["missed_profit"].clone().unwrap().parse::().unwrap(), - unique_inputs: row["unique_inputs"].clone().unwrap().parse::().unwrap(), + missed_profit: row["missed_profit"] + .clone() + .unwrap() + .parse::() + .unwrap(), + unique_inputs: row["unique_inputs"] + .clone() + .unwrap() + .parse::() + .unwrap(), }); true }); @@ -185,8 +205,7 @@ async fn echo_stats( } Err(err) => Ok(Response::new(full(format!("error:{}", err)))), } -} - +} async fn echo_info( param: &str, @@ -332,7 +351,7 @@ async fn echo_push( *response_not_enable.status_mut() = StatusCode::BAD_REQUEST; let netconfig = MyConfig::get_net_config(cfg, param); if !netconfig.enabled { - trace!("network not enabled {}",&netconfig.name); + trace!("network not enabled {}", &netconfig.name); return Ok(response_not_enable); } let req_time = Utc::now().timestamp_nanos_opt().unwrap(); // Returns i64 @@ -438,7 +457,7 @@ async fn echo_push( } if address == our_address && amount.to_sat() >= netconfig.fixed_fee { our_fees = amount.to_sat(); - our_address = netconfig.address.to_string(); + //our_address = netconfig.address.to_string(); found = true; trace!("address and fees are correct {}: {}", our_address, our_fees); } @@ -541,7 +560,7 @@ async fn echo( } Method::GET => { if let Some(param) = match_uri(r"^?/?(?P[^/]?+)?/stats$", uri.as_str()) { - ret = echo_stats(param, cfg, &remote_addr).await; + ret = echo_stats(param, cfg).await; } if let Some(param) = match_uri(r"^?/?(?P[^/]?+)?/info$", uri.as_str()) { ret = echo_info(param, cfg, &remote_addr).await; diff --git a/src/xpub.rs b/src/xpub.rs index afa4591..38e41f9 100644 --- a/src/xpub.rs +++ b/src/xpub.rs @@ -1,3 +1,4 @@ +//use bs58; use bitcoin::Address; use bitcoin::Network; use bitcoin::ScriptBuf; @@ -12,18 +13,141 @@ use std::str::FromStr; // Mainnet (BIP44/BIP49/BIP84) enum BS58Prefix { Xpub, - //Ypub, - //Zpub, - //Tpub, - //Vpub, - //Upub + Ypub, + Zpub, + Tpub, + Vpub, + Upub, } const XPUB_PREFIX: [u8; 4] = [0x04, 0x88, 0xB2, 0x1E]; // xpub (Legacy P2PKH) -//const YPUB_PREFIX:[u8; 4] = [0x04, 0x9D, 0x7C, 0xB2]; // ypub (Nested SegWit P2SH-P2WPKH) -//const ZPUB_PREFIX:[u8; 4] = [0x04, 0xB2, 0x47, 0x46]; // zpub (Native SegWit P2WPKH) -//const TPUB_PREFIX:[u8; 4] = [0x04, 0x35, 0x87, 0xCF]; // tpub (Testnet Legacy P2PKH) -//const VPUB_PREFIX:[u8; 4] = [0x04, 0x5F, 0x1C, 0xF6]; // vpub (Testnet Nested SegWit) -//const UPUB_PREFIX:[u8; 4] = [0x04, 0x4A, 0x52, 0x62]; // upub (RegTest Nested SegWit) +const YPUB_PREFIX: [u8; 4] = [0x04, 0x9D, 0x7C, 0xB2]; // ypub (Nested SegWit P2SH-P2WPKH) +const ZPUB_PREFIX: [u8; 4] = [0x04, 0xB2, 0x47, 0x46]; // zpub (Native SegWit P2WPKH) +const TPUB_PREFIX: [u8; 4] = [0x04, 0x35, 0x87, 0xCF]; // tpub (Testnet Legacy P2PKH) +const VPUB_PREFIX: [u8; 4] = [0x04, 0x5F, 0x1C, 0xF6]; // vpub (Testnet Nested SegWit) +const UPUB_PREFIX: [u8; 4] = [0x04, 0x4A, 0x52, 0x62]; // upub (RegTest Nested SegWit) +// Constants from Bitcoin Core's checksum algorithm +const INPUT_CHARSET: &[u8] = b"0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; +const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +// Polynomial modulo function used in checksum calculation (same as in Bitcoin Core) +fn poly_mod(mut c: u64, val: u64) -> u64 { + let c0 = c >> 35; + c = ((c & 0x7ffffffff) << 5) ^ val; + if c0 & 1 > 0 { + c ^= 0xf5dee51989 + }; + if c0 & 2 > 0 { + c ^= 0xa9fdca3312 + }; + if c0 & 4 > 0 { + c ^= 0x1bab10e32d + }; + if c0 & 8 > 0 { + c ^= 0x3706b1677a + }; + if c0 & 16 > 0 { + c ^= 0x644d626ffd + }; + + c +} + +// Calculate checksum for a descriptor string +fn calc_checksum(desc: &str) -> Result { + // Separate descriptor from any existing checksum + let desc = match desc.split_once('#') { + Some((d, _)) => d, + None => desc, + }; + + let mut c: u64 = 1; + let mut cls: u64 = 0; + let mut clscount: u64 = 0; + + // Process each character in the descriptor + for ch in desc.as_bytes() { + let pos = match INPUT_CHARSET.iter().position(|b| b == ch) { + Some(p) => p as u64, + None => return Err(format!("Invalid character in descriptor: {}", *ch as char)), + }; + + c = poly_mod(c, pos & 31); + cls = cls * 3 + (pos >> 5); + clscount += 1; + + if clscount == 3 { + c = poly_mod(c, cls); + cls = 0; + clscount = 0; + } + } + + if clscount > 0 { + c = poly_mod(c, cls); + } + + // Final steps in checksum calculation + for _ in 0..8 { + c = poly_mod(c, 0); + } + c ^= 1; + + // Convert checksum to characters + let mut checksum = String::with_capacity(8); + for j in 0..8 { + let idx = ((c >> (5 * (7 - j))) & 31) as usize; + checksum.push(CHECKSUM_CHARSET[idx] as char); + } + + Ok(checksum) +} + +pub fn get_bitcoincore_descriptor(xpub: &String) -> String { + let fingerprint = calculate_fingerprint(xpub); + let mut bip = 84; + let cpub = xpub.to_string(); + match &xpub[0..4] { + "vpub" => { + bip = 84; + } + "zpub" => { + bip = 84; + } + &_ => { + bip = 84; + } + }; + let descriptor = format!( + "wpkh([{}/84h/0h/0h]{}/0/*)", + fingerprint, + convert_xpub(xpub) + ); + let descriptor = match calc_checksum(&descriptor) { + Ok(checksum) => { + let clean_descriptor = descriptor.split('#').next().unwrap_or(&descriptor); + format!("{}#{}", clean_descriptor, checksum) + } + Err(err) => { + eprintln!("Error: {}", err); + "".to_string() + } + }; + descriptor + //format!("{}#{}",descriptor,checksum) +} +fn convert_xpub(xpub: &String) -> String { + if xpub[0..4] == *"xpub" || xpub[0..4] == *"ypub" || xpub[0..4] == *"zpub" { + return convert_to(xpub, BS58Prefix::Xpub).unwrap(); + } else { + return convert_to(xpub, BS58Prefix::Tpub).unwrap(); + } +} +pub fn calculate_fingerprint(tpub: &str) -> String { + let xpub = Xpub::from_str(&convert_to(tpub, BS58Prefix::Xpub).unwrap()).unwrap(); + let fp = xpub.fingerprint(); + let pp = xpub.parent_fingerprint; + format!("{}", fp) +} fn base58check_decode(s: &str) -> Result, String> { let data = bs58::decode(s).into_vec().map_err(|e| e.to_string())?; @@ -31,7 +155,7 @@ fn base58check_decode(s: &str) -> Result, String> { return Err("Data troppo corta".to_string()); } let (payload, checksum) = data.split_at(data.len() - 4); - let hash = Sha256::digest(Sha256::digest(payload)); + let hash = Sha256::digest(&Sha256::digest(payload)); if hash[0..4] != checksum[..] { return Err("Checksum invalido".to_string()); } @@ -39,7 +163,7 @@ fn base58check_decode(s: &str) -> Result, String> { } fn base58check_encode(data: &[u8]) -> String { - let checksum = &Sha256::digest(Sha256::digest(data))[0..4]; + let checksum = &Sha256::digest(&Sha256::digest(data))[0..4]; let full = [data, checksum].concat(); bs58::encode(full).into_string() } @@ -54,11 +178,11 @@ fn convert_to(zpub: &str, prefix: BS58Prefix) -> Result { 0..4, match prefix { BS58Prefix::Xpub => XPUB_PREFIX, - //BS58Prefix::Ypub => YPUB_PREFIX, - //BS58Prefix::Zpub => ZPUB_PREFIX, - //BS58Prefix::Vpub => VPUB_PREFIX, - //BS58Prefix::Tpub => TPUB_PREFIX, - //BS58Prefix::Upub => UPUB_PREFIX, + BS58Prefix::Ypub => YPUB_PREFIX, + BS58Prefix::Zpub => ZPUB_PREFIX, + BS58Prefix::Vpub => VPUB_PREFIX, + BS58Prefix::Tpub => TPUB_PREFIX, + BS58Prefix::Upub => UPUB_PREFIX, }, ); @@ -71,7 +195,7 @@ pub fn new_address_from_xpub( ) -> Result<(String, String), Box> { let xpub = Xpub::from_str(&convert_to(zpub, BS58Prefix::Xpub)?)?; let path = format!("m/0/{}", index); - let derivation_path = DerivationPath::from_str(path.as_str())?; + let derivation_path = DerivationPath::from_str(&path.as_str())?; let secp = Secp256k1::new(); let derived_xpub = xpub.derive_pub(&secp, &derivation_path)?; let public_key = derived_xpub.public_key; @@ -85,15 +209,17 @@ pub fn new_address_from_xpub( } /* fn main() -> Result<(), Box>{ - //let zpub = "xpub6C29v8gxCXREHUzoGNfqqFqZWxTVEmYtmZshuzfSwBKNmfYQxoizRziCkkUUA4WwJZkJs2i7nttRiC6MQG7mxZpouXeYkTZe3U52RyPAeo2"; - //let zpub = "vpub5Ut36m34VebUUjdhYaxJCjSPqk3ZR8bA2MXLmbHRQCycAxy5Q1GFPJspLkJywJjBgQnvU3rmwPKTPp1ELLWeXrve3zBufpZR4MRCCTNHzsn"; - let zpub = "zpub6qdfveGrxBQN3z8paZ88EHpCn5MGXpUoHwQmHhPbj4rPQtUjbWyCHrJFYZGVY7MsmVbDaeu4JYqRqcdLzMx78wZFEWbLrF9FG3gr2MPQC5H"; - match convert_to(zpub,BS58Prefix::Xpub) { - Ok(xpub) => println!("XPUB: {}", xpub), + match convert_to(zpub,BS58Prefix::Tpub) { + Ok(tpub) => println!("XPUB: {}", tpub), Err(e) => eprintln!("Errore: {}", e), } - let xpub = Xpub::from_str(&convert_to(zpub,BS58Prefix::Xpub)?)?; + let fingerprint = base58check_encode(&calculate_fingerprint(zpub)); + println!("ZPUB: {}, FINGERPRINT: {}",zpub,fingerprint); + let xpub = Xpub::from_str(&convert_to(zpub,BS58Prefix::Xpub)?)?; + let tpub = convert_to(zpub,BS58Prefix::Tpub)?; + let fingerprint = base58check_encode(&calculate_fingerprint(&tpub)); + println!("TPUB: {}, FINGERPRINT: {}",tpub,fingerprint); let derivation_path = DerivationPath::from_str("m/0/0")?; let secp = Secp256k1::new(); let derived_xpub = xpub.derive_pub(&secp, &derivation_path)?; @@ -110,5 +236,4 @@ fn main() -> Result<(), Box>{ let address = Address::from_script(&script_pubkey, network)?; Ok(()) -} -*/ +}*/