From 6f594e3b90cf9efbb32ad088de968d7e68da51e4 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sat, 11 Nov 2023 08:49:38 -0700 Subject: [PATCH 1/4] Return txid from transfer --- lib/web/rgb.ts | 3 +++ src/rgb.rs | 18 ++++++++++++++---- src/rgb/prebuild.rs | 6 +++--- src/rgb/structs.rs | 2 +- src/rgb/transfer.rs | 1 + src/structs.rs | 7 +++++++ 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/web/rgb.ts b/lib/web/rgb.ts index 514a9e19..0de97bba 100644 --- a/lib/web/rgb.ts +++ b/lib/web/rgb.ts @@ -534,6 +534,8 @@ export interface RgbTransferResponse { psbt: string; /// Tapret Commitment (used to spend output) commit: string; + /// Transfer Bitcoin L1 transaction id + txid: string; } export interface AcceptRequest { @@ -757,6 +759,7 @@ export interface BatchRgbTransferItem { status: TxStatus; isAccept: boolean; iface: string; + txid: string; } export interface RgbOfferRequest { diff --git a/src/rgb.rs b/src/rgb.rs index 4a13858a..d8c4eb48 100644 --- a/src/rgb.rs +++ b/src/rgb.rs @@ -732,6 +732,7 @@ pub async fn full_transfer_asset( commit, outpoint, amount, + txid, .. } = internal_transfer_asset( transfer_req, @@ -763,6 +764,7 @@ pub async fn full_transfer_asset( consig, psbt, commit, + txid, }; rgb_account.clone().update(&mut rgb_account_changes); @@ -796,6 +798,7 @@ pub async fn transfer_asset( commit, outpoint, amount, + txid, .. } = internal_transfer_asset( request.clone(), @@ -827,6 +830,7 @@ pub async fn transfer_asset( consig, psbt, commit, + txid, }; store_stock_account_transfers(sk, stock, rgb_account, rgb_transfers) @@ -1463,7 +1467,7 @@ async fn internal_transfer_asset( Some(psbt), ); - internal_save_transfer(internal_request, rgb_transfers) + let txid = internal_save_transfer(internal_request, rgb_transfers) .await .map_err(TransferError::WrongSave)?; @@ -1475,6 +1479,7 @@ async fn internal_transfer_asset( commit, outpoint: outpoint.to_string(), consigs, + txid: txid.to_hex(), }; Ok(resp) @@ -1497,6 +1502,7 @@ pub async fn internal_replace_transfer( outpoint, consigs, amount, + txid, .. } = internal_transfer_asset( request.clone(), @@ -1529,6 +1535,7 @@ pub async fn internal_replace_transfer( psbt, commit, consigs, + txid, }; store_stock_account_transfers(sk, stock, rgb_account, rgb_transfers) @@ -1649,7 +1656,7 @@ pub async fn save_transfer( pub async fn internal_save_transfer( request: RgbInternalSaveTransferRequest, rgb_transfers: &mut RgbTransfersV1, -) -> Result<(), SaveTransferError> { +) -> Result { let RgbInternalSaveTransferRequest { iface, consig: consignment, @@ -1662,7 +1669,7 @@ pub async fn internal_save_transfer( let RgbExtractTransfer { consig_id, contract_id, - tx_id, + txid: tx_id, strict, .. } = prebuild_extract_transfer(&consignment)?; @@ -1713,7 +1720,7 @@ pub async fn internal_save_transfer( .await .map_err(SaveTransferError::Proxy)?; - Ok(()) + Ok(tx_id) } pub async fn remove_transfer( @@ -2046,6 +2053,7 @@ pub async fn internal_verify_transfers( contract_id: contract_id.clone(), consig_id: activity.consig_id.to_string(), is_mine: activity.sender, + txid: txid.to_hex(), }); continue; } @@ -2061,6 +2069,7 @@ pub async fn internal_verify_transfers( contract_id: contract_id.clone(), consig_id: transfer_id.to_string(), is_mine: activity.sender, + txid: txid.to_hex(), }); } else { transfers.push(BatchRgbTransferItem { @@ -2070,6 +2079,7 @@ pub async fn internal_verify_transfers( contract_id: contract_id.clone(), consig_id: transfer_id.to_string(), is_mine: activity.sender, + txid: txid.to_hex(), }); pending_transfers.push(activity.to_owned()); } diff --git a/src/rgb/prebuild.rs b/src/rgb/prebuild.rs index a2c9d8c9..ac948f73 100644 --- a/src/rgb/prebuild.rs +++ b/src/rgb/prebuild.rs @@ -898,8 +898,8 @@ pub fn prebuild_extract_transfer( let confined = Confined::try_from_iter(serialized.iter().copied()) .expect("invalid confined serialization"); - let (tx_id, transfer) = match extract_transfer(consignment.to_owned()) { - Ok((txid, tranfer)) => (txid, tranfer), + let (txid, transfer) = match extract_transfer(consignment.to_owned()) { + Ok((tx_id, transfer)) => (tx_id, transfer), Err(err) => return Err(SaveTransferError::WrongConsigSwap(err)), }; @@ -909,7 +909,7 @@ pub fn prebuild_extract_transfer( consig_id, strict: confined, contract_id, - tx_id, + txid, transfer, }) } diff --git a/src/rgb/structs.rs b/src/rgb/structs.rs index fd030987..2641792a 100644 --- a/src/rgb/structs.rs +++ b/src/rgb/structs.rs @@ -354,7 +354,7 @@ impl Default for RgbTransferV1 { pub struct RgbExtractTransfer { pub consig_id: String, pub contract_id: String, - pub tx_id: Txid, + pub txid: Txid, pub transfer: Bindle, pub strict: Confined, 0, U32>, } diff --git a/src/rgb/transfer.rs b/src/rgb/transfer.rs index 7cddf418..d3cda793 100644 --- a/src/rgb/transfer.rs +++ b/src/rgb/transfer.rs @@ -231,6 +231,7 @@ pub fn extract_transfer(transfer: String) -> Result<(Txid, Bindle), Ac if transfer.known_transitions_by_bundle_id(bundle_id).is_none() { return Err(AcceptTransferError::Inconclusive); }; + // This only returns the first anchor txid if let Some(AnchoredBundle { anchor, bundle: _ }) = transfer.anchored_bundle(bundle_id) { return Ok((anchor.txid, Bindle::new(transfer))); } diff --git a/src/structs.rs b/src/structs.rs index 4d6858f1..98dcb19b 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -878,6 +878,8 @@ pub struct RgbTransferResponse { pub psbt: String, /// Tapret Commitment (used to spend output) pub commit: String, + /// Transfer Bitcoin L1 transaction id + pub txid: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -893,6 +895,8 @@ pub struct RgbReplaceResponse { pub commit: String, /// Strict Consignments (in hexadecimal) pub consigs: BTreeMap, + /// Transfer Bitcoin L1 transaction id + pub txid: String, } #[derive(Serialize, Deserialize, Debug, Clone, Default)] @@ -912,6 +916,8 @@ pub struct RgbInternalTransferResponse { pub commit: String, /// Strict Consignments (in hexadecimal) pub consigs: BTreeMap, + /// Transfer Bitcoin L1 transaction id + pub txid: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -1281,6 +1287,7 @@ pub struct BatchRgbTransferItem { pub status: TxStatus, pub is_accept: bool, pub is_mine: bool, + pub txid: String, } #[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, Debug, Display)] From 3266dc2d7b2355a461b29af271a5037e846a2e53 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sat, 11 Nov 2023 07:54:58 -0800 Subject: [PATCH 2/4] Return txid from fund vault. Fix tests. --- lib/web/bitcoin.ts | 2 ++ src/bitcoin.rs | 2 ++ src/structs.rs | 1 + tests/rgb/integration/transfers.rs | 3 +++ 4 files changed, 8 insertions(+) diff --git a/lib/web/bitcoin.ts b/lib/web/bitcoin.ts index ddfd0509..2fc3f892 100644 --- a/lib/web/bitcoin.ts +++ b/lib/web/bitcoin.ts @@ -204,4 +204,6 @@ export interface WalletData { export interface FundVaultDetails { assetsOutput?: string; udasOutput?: string; + isFunded: boolean; + fundTxid: string; } diff --git a/src/bitcoin.rs b/src/bitcoin.rs index 87ecabef..1b0c955a 100644 --- a/src/bitcoin.rs +++ b/src/bitcoin.rs @@ -425,6 +425,7 @@ pub async fn fund_vault( assets_output, udas_output, is_funded: true, + fund_txid: Some(asset_txid.to_hex()), }) } @@ -459,6 +460,7 @@ pub async fn get_assets_vault( assets_output, udas_output, is_funded, + fund_txid: None, }) } diff --git a/src/structs.rs b/src/structs.rs index 98dcb19b..35b1caac 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -102,6 +102,7 @@ pub struct FundVaultDetails { pub assets_output: Option, pub udas_output: Option, pub is_funded: bool, + pub fund_txid: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/tests/rgb/integration/transfers.rs b/tests/rgb/integration/transfers.rs index 1ffa4e61..01513d61 100644 --- a/tests/rgb/integration/transfers.rs +++ b/tests/rgb/integration/transfers.rs @@ -1906,6 +1906,7 @@ async fn allow_consecutive_full_transfer_bidirectional() -> anyhow::Result<()> { consig, psbt, commit: _, + .. } = full_transfer_resp; let request = SignPsbtRequest { @@ -1969,6 +1970,7 @@ async fn allow_consecutive_full_transfer_bidirectional() -> anyhow::Result<()> { consig, psbt, commit: _, + .. } = full_transfer_resp; let request = SignPsbtRequest { @@ -2082,6 +2084,7 @@ async fn allow_save_transfer_and_verify() -> anyhow::Result<()> { consig, psbt, commit: _, + .. } = resp; let request = SignPsbtRequest { From b7555ad920a5a2f8720fe3b09aeb398891fc1d06 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sat, 11 Nov 2023 09:32:36 -0800 Subject: [PATCH 3/4] Bump version to 0.7.0-beta.8. --- Cargo.lock | 2 +- Cargo.toml | 2 +- lib/web/package-lock.json | 4 ++-- lib/web/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1d35e90..f3144ae4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,7 +649,7 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bitmask-core" -version = "0.7.0-beta.7" +version = "0.7.0-beta.8" dependencies = [ "amplify", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index e69c2b32..6e7d11f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitmask-core" -version = "0.7.0-beta.7" +version = "0.7.0-beta.8" authors = [ "Jose Diego Robles ", "Hunter Trujillo ", diff --git a/lib/web/package-lock.json b/lib/web/package-lock.json index 9a7114a8..88fa2a3f 100644 --- a/lib/web/package-lock.json +++ b/lib/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitmask-core", - "version": "0.7.0-beta.7", + "version": "0.7.0-beta.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bitmask-core", - "version": "0.7.0-beta.7", + "version": "0.7.0-beta.8", "license": "MIT", "devDependencies": { "@types/node": "^20.8.2", diff --git a/lib/web/package.json b/lib/web/package.json index 781ce066..fecd44f0 100644 --- a/lib/web/package.json +++ b/lib/web/package.json @@ -6,7 +6,7 @@ "Francisco Calderón " ], "description": "Core functionality for the BitMask wallet", - "version": "0.7.0-beta.7", + "version": "0.7.0-beta.8", "license": "MIT", "repository": { "type": "git", From e75d5cd6a74dd0cf84c397a9c50d87db94e14c6f Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sun, 12 Nov 2023 08:16:39 -0800 Subject: [PATCH 4/4] Add csv and json wallet metrics endpoints. --- Cargo.lock | 20 ++++ Cargo.toml | 1 + src/bin/bitmaskd.rs | 24 ++++- src/carbonado.rs | 1 + src/carbonado/metrics.rs | 204 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/carbonado/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index f3144ae4..126872af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -715,6 +715,7 @@ dependencies = [ "tokio", "toml 0.7.8", "tower-http", + "walkdir", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -2937,6 +2938,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.22" @@ -3989,6 +3999,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 6e7d11f6..9fe76c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ strict_types = "1.6.3" thiserror = "1.0" tokio = { version = "1.33.0", features = ["macros", "sync"] } zeroize = "1.6.0" +walkdir = "2.4.0" [target.'cfg(target_arch = "wasm32")'.dependencies] bdk = { version = "0.28.2", features = [ diff --git a/src/bin/bitmaskd.rs b/src/bin/bitmaskd.rs index ba4ed651..272520c5 100644 --- a/src/bin/bitmaskd.rs +++ b/src/bin/bitmaskd.rs @@ -17,7 +17,7 @@ use axum::{ use bitcoin_30::secp256k1::{ecdh::SharedSecret, PublicKey, SecretKey}; use bitmask_core::{ bitcoin::{save_mnemonic, sign_and_publish_psbt_file}, - carbonado::{handle_file, server_retrieve, server_store, store}, + carbonado::{handle_file, metrics::metrics_csv, server_retrieve, server_store, store}, constants::{ get_marketplace_nostr_key, get_marketplace_seed, get_network, get_udas_utxo, switch_network, }, @@ -704,11 +704,27 @@ async fn send_coins( Path((address, amount)): Path<(String, String)>, ) -> Result { use bitmask_core::regtest::send_coins; - send_coins(&address, &amount); + Ok("Ok") } +async fn json_metrics() -> Result { + use bitmask_core::carbonado::metrics::metrics; + let path = std::env::var("CARBONADO_DIR").unwrap_or("/tmp/bitmaskd/carbonado".to_owned()); + let dir = std::path::Path::new(&path); + + Ok(Json(metrics(dir)?)) +} + +async fn csv_metrics() -> Result { + use bitmask_core::carbonado::metrics::metrics; + let path = std::env::var("CARBONADO_DIR").unwrap_or("/tmp/bitmaskd/carbonado".to_owned()); + let dir = std::path::Path::new(&path); + + Ok(metrics_csv(metrics(dir)?)) +} + #[tokio::main] async fn main() -> Result<()> { if env::var("RUST_LOG").is_err() { @@ -759,7 +775,9 @@ async fn main() -> Result<()> { .route("/proxy/consignment/:id", get(rgb_proxy_consig_retrieve)) .route("/proxy/media-metadata", post(rgb_proxy_media_data_save)) .route("/proxy/media-metadata/:id", get(rgb_proxy_media_retrieve)) - .route("/proxy/media/:id", get(rgb_proxy_metadata_retrieve)); + .route("/proxy/media/:id", get(rgb_proxy_metadata_retrieve)) + .route("/metrics.json", get(json_metrics)) + .route("/metrics.csv", get(csv_metrics)); let network = get_network().await; switch_network(&network).await?; diff --git a/src/carbonado.rs b/src/carbonado.rs index eb32bb2d..d91a40e6 100644 --- a/src/carbonado.rs +++ b/src/carbonado.rs @@ -4,6 +4,7 @@ use bitcoin_30::secp256k1::{PublicKey, SecretKey}; use crate::{carbonado::error::CarbonadoError, constants::NETWORK, info, structs::FileMetadata}; pub mod error; +pub mod metrics; #[cfg(not(target_arch = "wasm32"))] pub use server::{handle_file, retrieve, retrieve_metadata, server_retrieve, server_store, store}; diff --git a/src/carbonado/metrics.rs b/src/carbonado/metrics.rs new file mode 100644 index 00000000..21971d12 --- /dev/null +++ b/src/carbonado/metrics.rs @@ -0,0 +1,204 @@ +use std::{collections::BTreeMap, path::Path, time::SystemTime}; + +use anyhow::Result; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use walkdir::WalkDir; + +#[derive(Serialize, Deserialize, Default)] +pub struct MetricsResponse { + bytes: u64, + bytes_by_day: BTreeMap, + bitcoin_wallets_by_day: BTreeMap, + wallets_by_network: BTreeMap, +} + +pub fn metrics(dir: &Path) -> Result { + let mut response = MetricsResponse::default(); + + response.wallets_by_network.insert("bitcoin".to_string(), 0); + response.wallets_by_network.insert("testnet".to_string(), 0); + response.wallets_by_network.insert("signet".to_string(), 0); + response.wallets_by_network.insert("regtest".to_string(), 0); + response.wallets_by_network.insert("total".to_string(), 0); + + let mut total_wallets = 0; + let mut day_prior = None; + + for entry in WalkDir::new(dir) { + let entry = entry?; + let filename = entry.file_name().to_string_lossy().to_string(); + let metadata = entry.metadata()?; + let day = metadata.created()?; + let day = round_system_time_to_day(day); + + if metadata.is_file() { + response.bytes += metadata.len(); + + let bytes_total_day_prior = if let Some(dp) = &day_prior { + response.bytes_by_day.get(dp).unwrap_or(&0).to_owned() + } else { + 0 + }; + + let bitcoin_wallets_total_day_prior = if let Some(dp) = day_prior { + response + .bitcoin_wallets_by_day + .get(&dp) + .unwrap_or(&0) + .to_owned() + } else { + 0 + }; + + *response + .bytes_by_day + .entry(day.clone()) + .or_insert(bytes_total_day_prior) += metadata.len(); + + if filename + == "bitcoin-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + { + *response + .wallets_by_network + .get_mut("bitcoin") + .unwrap_or(&mut 0) += 1; + *response + .bitcoin_wallets_by_day + .entry(day.clone()) + .or_insert(bitcoin_wallets_total_day_prior) += 1; + } + + if filename + == "testnet-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + { + *response + .wallets_by_network + .get_mut("testnet") + .unwrap_or(&mut 0) += 1; + } + + if filename + == "signet-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + { + *response + .wallets_by_network + .get_mut("signet") + .unwrap_or(&mut 0) += 1; + } + + if filename + == "regtest-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + { + *response + .wallets_by_network + .get_mut("regtest") + .unwrap_or(&mut 0) += 1; + } + + if filename == "bitcoin-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + || filename == "testnet-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + || filename == "signet-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + || filename == "regtest-6075e9716c984b37840f76ad2b50b3d1b98ed286884e5ceba5bcc8e6b74988d3.c15" + { + total_wallets += 1; + } + } + + day_prior = Some(day); + } + + *response + .wallets_by_network + .get_mut("total") + .unwrap_or(&mut 0) = total_wallets; + + Ok(response) +} + +pub fn metrics_csv(metrics: MetricsResponse) -> String { + let lines = vec![vec![ + "Wallet", + "Wallet Count", + "Bytes Total", + "Day", + "Bitcoin Wallets by Day", + "Bytes by Day", + ]]; + + for (day, bitcoin_wallets) in metrics.bitcoin_wallets_by_day { + let mut line = vec![]; + + if lines.len() == 1 { + line.push("Bitcoin".to_string()); + line.push( + metrics + .wallets_by_network + .get("bitcoin") + .unwrap() + .to_string(), + ); + line.push(metrics.bytes.to_string()); + } + + if lines.len() == 2 { + line.push("Tesnet".to_string()); + line.push( + metrics + .wallets_by_network + .get("testnet") + .unwrap() + .to_string(), + ); + line.push("".to_owned()); + } + + if lines.len() == 3 { + line.push("signet".to_string()); + line.push( + metrics + .wallets_by_network + .get("signet") + .unwrap() + .to_string(), + ); + line.push("".to_owned()); + } + + if lines.len() == 4 { + line.push("regtest".to_string()); + line.push( + metrics + .wallets_by_network + .get("regtest") + .unwrap() + .to_string(), + ); + line.push("".to_owned()); + } + + if lines.len() > 4 { + line.push("".to_owned()); + line.push("".to_owned()); + line.push("".to_owned()); + } + + line.push(day.clone()); + line.push(bitcoin_wallets.to_string()); + line.push(metrics.bytes_by_day.get(&day).unwrap().to_string()) + } + + let lines: Vec = lines.iter().map(|line| line.join(",")).collect(); + lines.join("\n") +} + +fn round_system_time_to_day(system_time: SystemTime) -> String { + // Convert SystemTime to a Chrono DateTime + let datetime: DateTime = system_time.into(); + + // Round down to the nearest day (00:00:00) + let rounded = datetime.date_naive().and_hms_opt(0, 0, 0).unwrap(); + + // Format the date as a string in "YYYY-MM-DD" format + rounded.format("%Y-%m-%d").to_string() +}