global: snapshot + monitor: add addresses to mempool

This commit is contained in:
nym21
2025-10-14 17:36:16 +02:00
parent db0298ac1b
commit 5425085953
63 changed files with 707 additions and 330 deletions
+17 -28
View File
@@ -1,6 +1,6 @@
use std::fmt;
use bitcoin::{Network, ScriptBuf, opcodes, script::Builder};
use bitcoin::{ScriptBuf, opcodes, script::Builder};
use brk_error::Error;
use derive_deref::Deref;
use schemars::JsonSchema;
@@ -29,64 +29,53 @@ impl From<String> for Address {
}
}
impl TryFrom<ScriptBuf> for Address {
impl TryFrom<&ScriptBuf> for Address {
type Error = Error;
fn try_from(script: ScriptBuf) -> Result<Self, Self::Error> {
Ok(Self::from(bitcoin::Address::from_script(
&script,
Network::Bitcoin,
)?))
}
}
impl From<bitcoin::Address> for Address {
fn from(address: bitcoin::Address) -> Self {
Self {
address: address.to_string(),
}
fn try_from(script: &ScriptBuf) -> Result<Self, Self::Error> {
Self::try_from(&AddressBytes::try_from(script)?)
}
}
impl TryFrom<(&ScriptBuf, OutputType)> for Address {
type Error = Error;
fn try_from(tuple: (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
Self::try_from(AddressBytes::try_from(tuple)?)
Self::try_from(&AddressBytes::try_from(tuple)?)
}
}
impl TryFrom<AddressBytes> for Address {
impl TryFrom<&AddressBytes> for Address {
type Error = Error;
fn try_from(bytes: AddressBytes) -> Result<Self, Self::Error> {
fn try_from(bytes: &AddressBytes) -> Result<Self, Self::Error> {
let address = match bytes {
AddressBytes::P2PK65(b) => Self::from(bytes_to_hex(&**b)),
AddressBytes::P2PK33(b) => Self::from(bytes_to_hex(&**b)),
AddressBytes::P2PK65(_) => Self::from(bytes_to_hex(bytes.as_slice())),
AddressBytes::P2PK33(_) => Self::from(bytes_to_hex(bytes.as_slice())),
AddressBytes::P2PKH(b) => Self::try_from(
Builder::new()
&Builder::new()
.push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(**b)
.push_slice(****b)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script(),
)?,
AddressBytes::P2SH(b) => Self::try_from(
Builder::new()
&Builder::new()
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(**b)
.push_slice(****b)
.push_opcode(opcodes::all::OP_EQUAL)
.into_script(),
)?,
AddressBytes::P2WPKH(b) => {
Self::try_from(Builder::new().push_int(0).push_slice(**b).into_script())?
Self::try_from(&Builder::new().push_int(0).push_slice(****b).into_script())?
}
AddressBytes::P2WSH(b) => {
Self::try_from(Builder::new().push_int(0).push_slice(**b).into_script())?
Self::try_from(&Builder::new().push_int(0).push_slice(****b).into_script())?
}
AddressBytes::P2TR(b) => {
Self::try_from(Builder::new().push_int(1).push_slice(**b).into_script())?
Self::try_from(&Builder::new().push_int(1).push_slice(****b).into_script())?
}
AddressBytes::P2A(b) => {
Self::try_from(Builder::new().push_int(1).push_slice(**b).into_script())?
Self::try_from(&Builder::new().push_int(1).push_slice(****b).into_script())?
}
};
Ok(address)
+40 -25
View File
@@ -1,3 +1,5 @@
use std::fmt;
use bitcoin::ScriptBuf;
use brk_error::Error;
@@ -6,16 +8,16 @@ use super::{
P2WSHBytes,
};
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddressBytes {
P2PK65(P2PK65Bytes),
P2PK33(P2PK33Bytes),
P2PKH(P2PKHBytes),
P2SH(P2SHBytes),
P2WPKH(P2WPKHBytes),
P2WSH(P2WSHBytes),
P2TR(P2TRBytes),
P2A(P2ABytes),
P2PK65(Box<P2PK65Bytes>), // 65
P2PK33(Box<P2PK33Bytes>), // 33
P2PKH(Box<P2PKHBytes>), // 20
P2SH(Box<P2SHBytes>), // 20
P2WPKH(Box<P2WPKHBytes>), // 20
P2WSH(Box<P2WSHBytes>), // 32
P2TR(Box<P2TRBytes>), // 32
P2A(Box<P2ABytes>), // 2
}
impl AddressBytes {
@@ -33,6 +35,19 @@ impl AddressBytes {
}
}
impl fmt::Display for AddressBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&super::Address::try_from(self).unwrap().to_string())
}
}
impl TryFrom<&ScriptBuf> for AddressBytes {
type Error = Error;
fn try_from(script: &ScriptBuf) -> Result<Self, Self::Error> {
Self::try_from((script, OutputType::from(script)))
}
}
impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
type Error = Error;
fn try_from(tuple: (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
@@ -48,7 +63,7 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
return Err(Error::WrongLength);
}
};
Ok(Self::P2PK65(P2PK65Bytes::from(bytes)))
Ok(Self::P2PK65(Box::new(P2PK65Bytes::from(bytes))))
}
OutputType::P2PK33 => {
let bytes = script.as_bytes();
@@ -59,31 +74,31 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
return Err(Error::WrongLength);
}
};
Ok(Self::P2PK33(P2PK33Bytes::from(bytes)))
Ok(Self::P2PK33(Box::new(P2PK33Bytes::from(bytes))))
}
OutputType::P2PKH => {
let bytes = &script.as_bytes()[3..23];
Ok(Self::P2PKH(P2PKHBytes::from(bytes)))
Ok(Self::P2PKH(Box::new(P2PKHBytes::from(bytes))))
}
OutputType::P2SH => {
let bytes = &script.as_bytes()[2..22];
Ok(Self::P2SH(P2SHBytes::from(bytes)))
Ok(Self::P2SH(Box::new(P2SHBytes::from(bytes))))
}
OutputType::P2WPKH => {
let bytes = &script.as_bytes()[2..];
Ok(Self::P2WPKH(P2WPKHBytes::from(bytes)))
Ok(Self::P2WPKH(Box::new(P2WPKHBytes::from(bytes))))
}
OutputType::P2WSH => {
let bytes = &script.as_bytes()[2..];
Ok(Self::P2WSH(P2WSHBytes::from(bytes)))
Ok(Self::P2WSH(Box::new(P2WSHBytes::from(bytes))))
}
OutputType::P2TR => {
let bytes = &script.as_bytes()[2..];
Ok(Self::P2TR(P2TRBytes::from(bytes)))
Ok(Self::P2TR(Box::new(P2TRBytes::from(bytes))))
}
OutputType::P2A => {
let bytes = &script.as_bytes()[2..];
Ok(Self::P2A(P2ABytes::from(bytes)))
Ok(Self::P2A(Box::new(P2ABytes::from(bytes))))
}
OutputType::P2MS => Err(Error::WrongAddressType),
OutputType::Unknown => Err(Error::WrongAddressType),
@@ -96,48 +111,48 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
impl From<P2PK65Bytes> for AddressBytes {
fn from(value: P2PK65Bytes) -> Self {
Self::P2PK65(value)
Self::P2PK65(Box::new(value))
}
}
impl From<P2PK33Bytes> for AddressBytes {
fn from(value: P2PK33Bytes) -> Self {
Self::P2PK33(value)
Self::P2PK33(Box::new(value))
}
}
impl From<P2PKHBytes> for AddressBytes {
fn from(value: P2PKHBytes) -> Self {
Self::P2PKH(value)
Self::P2PKH(Box::new(value))
}
}
impl From<P2SHBytes> for AddressBytes {
fn from(value: P2SHBytes) -> Self {
Self::P2SH(value)
Self::P2SH(Box::new(value))
}
}
impl From<P2WPKHBytes> for AddressBytes {
fn from(value: P2WPKHBytes) -> Self {
Self::P2WPKH(value)
Self::P2WPKH(Box::new(value))
}
}
impl From<P2WSHBytes> for AddressBytes {
fn from(value: P2WSHBytes) -> Self {
Self::P2WSH(value)
Self::P2WSH(Box::new(value))
}
}
impl From<P2TRBytes> for AddressBytes {
fn from(value: P2TRBytes) -> Self {
Self::P2TR(value)
Self::P2TR(Box::new(value))
}
}
impl From<P2ABytes> for AddressBytes {
fn from(value: P2ABytes) -> Self {
Self::P2A(value)
Self::P2A(Box::new(value))
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ use serde::Serialize;
/// Address statistics on the blockchain (confirmed transactions only)
///
/// Based on mempool.space's format with type_index extension.
#[derive(Debug, Serialize, JsonSchema)]
#[derive(Debug, Default, Serialize, JsonSchema)]
pub struct AddressChainStats {
/// Total number of transaction outputs that funded this address
#[schemars(example = 5)]
+28 -2
View File
@@ -1,4 +1,4 @@
use crate::Sats;
use crate::{Sats, TxOut};
use schemars::JsonSchema;
use serde::Serialize;
@@ -7,7 +7,7 @@ use serde::Serialize;
///
/// Based on mempool.space's format.
///
#[derive(Debug, Serialize, JsonSchema)]
#[derive(Debug, Default, Serialize, JsonSchema)]
pub struct AddressMempoolStats {
/// Number of unconfirmed transaction outputs funding this address
#[schemars(example = 0)]
@@ -29,3 +29,29 @@ pub struct AddressMempoolStats {
#[schemars(example = 0)]
pub tx_count: u32,
}
impl AddressMempoolStats {
pub fn receiving(&mut self, txout: &TxOut) {
self.funded_txo_count += 1;
self.funded_txo_sum += txout.value;
}
pub fn received(&mut self, txout: &TxOut) {
self.funded_txo_count -= 1;
self.funded_txo_sum -= txout.value;
}
pub fn sending(&mut self, txout: &TxOut) {
self.spent_txo_count += 1;
self.spent_txo_sum += txout.value;
}
pub fn sent(&mut self, txout: &TxOut) {
self.spent_txo_count -= 1;
self.spent_txo_sum -= txout.value;
}
pub fn update_tx_count(&mut self, tx_count: u32) {
self.tx_count = tx_count
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::{AddressChainStats, AddressMempoolStats};
use crate::{Address, AddressChainStats, AddressMempoolStats};
use schemars::JsonSchema;
use serde::Serialize;
@@ -9,7 +9,7 @@ pub struct AddressStats {
#[schemars(
example = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
)]
pub address: String,
pub address: Address,
/// Statistics for confirmed transactions on the blockchain
pub chain_stats: AddressChainStats,
+15
View File
@@ -9,11 +9,14 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
DerefMut,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct U8x2([u8; 2]);
impl From<&[u8]> for U8x2 {
@@ -31,11 +34,14 @@ impl From<&[u8]> for U8x2 {
DerefMut,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct U8x20([u8; 20]);
impl From<&[u8]> for U8x20 {
@@ -53,11 +59,14 @@ impl From<&[u8]> for U8x20 {
DerefMut,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct U8x32([u8; 32]);
impl From<&[u8]> for U8x32 {
@@ -75,11 +84,14 @@ impl From<&[u8]> for U8x32 {
DerefMut,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]);
impl From<&[u8]> for U8x33 {
@@ -97,11 +109,14 @@ impl From<&[u8]> for U8x33 {
DerefMut,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]);
impl From<&[u8]> for U8x65 {
+4 -6
View File
@@ -85,10 +85,9 @@ mod tx;
mod txid;
mod txidpath;
mod txidprefix;
mod txin;
mod txindex;
mod txinput;
mod txoutput;
mod txprevout;
mod txout;
mod txstatus;
mod txversion;
mod typeindex;
@@ -182,10 +181,9 @@ pub use tx::*;
pub use txid::*;
pub use txidpath::*;
pub use txidprefix::*;
pub use txin::*;
pub use txindex::*;
pub use txinput::*;
pub use txoutput::*;
pub use txprevout::*;
pub use txout::*;
pub use txstatus::*;
pub use txversion::*;
pub use typeindex::*;
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x2;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2ABytes(U8x2);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x33;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2PK33Bytes(U8x33);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x65;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2PK65Bytes(U8x65);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x20;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2PKHBytes(U8x20);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x20;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2SHBytes(U8x20);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x32;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2TRBytes(U8x32);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x20;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2WPKHBytes(U8x20);
+13 -1
View File
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::U8x32;
#[derive(
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
Debug,
Clone,
Deref,
PartialEq,
Eq,
PartialOrd,
Ord,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
Serialize,
Hash,
)]
pub struct P2WSHBytes(U8x32);
+56 -13
View File
@@ -1,43 +1,86 @@
use crate::{RawLockTime, Sats, TxIndex, TxInput, TxOutput, TxStatus, TxVersion, Txid};
use crate::{RawLockTime, Sats, TxIn, TxIndex, TxOut, TxStatus, TxVersion, Txid, Weight};
use bitcoincore_rpc::Client;
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::CheckedSub;
#[derive(Debug, Serialize, JsonSchema)]
/// Transaction information compatible with mempool.space API format
pub struct Tx {
#[schemars(example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba")]
pub txid: Txid,
pub struct Transaction {
#[schemars(example = TxIndex::new(0))]
pub index: TxIndex,
pub index: Option<TxIndex>,
#[schemars(example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")]
pub txid: Txid,
#[schemars(example = 2)]
pub version: TxVersion,
#[schemars(example = 0)]
pub locktime: RawLockTime,
#[serde(rename = "locktime")]
pub lock_time: RawLockTime,
/// Transaction size in bytes
#[schemars(example = 222)]
pub size: u32,
#[serde(rename = "size")]
pub total_size: usize,
/// Transaction weight (for SegWit transactions)
/// Transaction weight
#[schemars(example = 558)]
pub weight: u32,
pub weight: Weight,
/// Number of signature operations
#[schemars(example = 1)]
pub sigops: u32,
#[serde(rename = "sigops")]
pub total_sigop_cost: usize,
/// Transaction fee in satoshis
#[schemars(example = Sats::new(31))]
pub fee: Sats,
/// Transaction inputs
pub vin: Vec<TxInput>,
#[serde(rename = "vin")]
pub input: Vec<TxIn>,
/// Transaction outputs
pub vout: Vec<TxOutput>,
#[serde(rename = "vout")]
pub output: Vec<TxOut>,
pub status: TxStatus,
}
impl Transaction {
pub fn fee(&self) -> Option<Sats> {
let in_ = self
.input
.iter()
.map(|txin| txin.prevout.as_ref().map(|txout| txout.value))
.sum::<Option<Sats>>()?;
let out = self.output.iter().map(|txout| txout.value).sum::<Sats>();
Some(in_.checked_sub(out).unwrap())
}
}
impl Transaction {
pub fn from_mempool(tx: bitcoin::Transaction, rpc: &Client) -> Self {
let mut this = Self {
index: None,
txid: tx.compute_txid().into(),
version: tx.version.into(),
total_sigop_cost: tx.total_sigop_cost(|_| None),
weight: tx.weight().into(),
lock_time: tx.lock_time.into(),
total_size: tx.total_size(),
fee: Sats::default(),
input: tx
.input
.into_iter()
.map(|txin| TxIn::from((txin, rpc)))
.collect(),
output: tx.output.into_iter().map(TxOut::from).collect(),
status: TxStatus::UNCOMFIRMED,
};
this.fee = this.fee().unwrap_or_default();
this
}
}
+11 -1
View File
@@ -8,7 +8,17 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
/// Transaction ID (hash)
#[derive(
Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, JsonSchema,
Debug,
Deref,
Clone,
PartialEq,
Eq,
Immutable,
IntoBytes,
KnownLayout,
FromBytes,
JsonSchema,
Hash,
)]
#[schemars(
example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
+118
View File
@@ -0,0 +1,118 @@
use crate::{TxOut, Txid, Vout};
use bitcoin::{Script, ScriptBuf};
use bitcoincore_rpc::{Client, RpcApi};
use schemars::JsonSchema;
use serde::{Serialize, Serializer, ser::SerializeStruct};
#[derive(Debug, JsonSchema)]
/// Transaction input
pub struct TxIn {
/// Transaction ID of the output being spent
#[schemars(example = "0000000000000000000000000000000000000000000000000000000000000000")]
pub txid: Txid,
#[schemars(example = 0)]
pub vout: Vout,
/// Information about the previous output being spent
#[schemars(example = None as Option<TxOut>)]
pub prevout: Option<TxOut>,
/// Signature script (for non-SegWit inputs)
#[schemars(
rename = "scriptsig",
with = "String",
example = "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
)]
pub script_sig: ScriptBuf,
/// Signature script in assembly format
#[allow(dead_code)]
#[schemars(
rename = "scriptsig_asm",
with = "String",
example = "OP_PUSHBYTES_4 ffff001d OP_PUSHBYTES_1 04 OP_PUSHBYTES_69 5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
)]
script_sig_asm: (),
// /// Witness data (for SegWit inputs)
// #[schemars(example = vec!["3045022100d0c9936990bf00bdba15f425f0f360a223d5cbf81f4bf8477fe6c6d838fb5fae02207e42a8325a4dd41702bf065aa6e0a1b7b0b8ee92a5e6c182da018b0afc82c40601".to_string()])]
// pub witness: Vec<String>,
//
/// Whether this input is a coinbase (block reward) input
#[schemars(example = false)]
pub is_coinbase: bool,
/// Input sequence number
#[schemars(example = 429496729)]
pub sequence: u32,
/// Inner redeemscript in assembly format (for P2SH-wrapped SegWit)
#[allow(dead_code)]
#[schemars(
rename = "inner_redeemscript_asm",
with = "Option<String>",
example = Some("OP_0 OP_PUSHBYTES_20 992a1f7420fc5285070d19c71ff2efb1e356ad2f".to_string())
)]
inner_redeem_script_asm: (),
}
impl TxIn {
pub fn script_sig_asm(&self) -> String {
self.script_sig.to_asm_string()
}
pub fn redeem_script(&self) -> Option<&Script> {
self.script_sig.redeem_script()
}
}
impl From<(bitcoin::TxIn, &Client)> for TxIn {
fn from((txin, rpc): (bitcoin::TxIn, &Client)) -> Self {
let txout_result = rpc
.get_tx_out(
&txin.previous_output.txid,
txin.previous_output.vout,
Some(true),
)
.unwrap();
let is_coinbase = txout_result.as_ref().is_none_or(|r| r.coinbase);
// txin.witness
// txin.script_sig.redeem_script()
Self {
is_coinbase,
prevout: txout_result.map(TxOut::from),
txid: txin.previous_output.txid.into(),
vout: txin.previous_output.vout.into(),
script_sig: txin.script_sig,
script_sig_asm: (),
sequence: txin.sequence.into(),
// witness:
inner_redeem_script_asm: (),
}
}
}
impl Serialize for TxIn {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("TxIn", 8)?;
state.serialize_field("txid", &self.txid)?;
state.serialize_field("vout", &self.vout)?;
state.serialize_field("prevout", &self.prevout)?;
state.serialize_field("scriptsig", &self.script_sig.to_hex_string())?;
state.serialize_field("scriptsig_asm", &self.script_sig_asm())?;
state.serialize_field("is_coinbase", &self.is_coinbase)?;
state.serialize_field("sequence", &self.sequence)?;
state.serialize_field("inner_redeemscript_asm", &self.redeem_script())?;
state.end()
}
}
-40
View File
@@ -1,40 +0,0 @@
use crate::{TxPrevout, Txid, Vout};
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Transaction input
pub struct TxInput {
/// Transaction ID of the output being spent
pub txid: Txid,
#[schemars(example = 0)]
pub vout: Vout,
/// Information about the previous output being spent
pub prevout: Option<TxPrevout>,
/// Signature script (for non-SegWit inputs)
#[schemars(example = "")]
pub scriptsig: String,
/// Signature script in assembly format
#[schemars(example = "")]
pub scriptsig_asm: String,
/// Witness data (for SegWit inputs)
#[schemars(example = vec!["3045022100d0c9936990bf00bdba15f425f0f360a223d5cbf81f4bf8477fe6c6d838fb5fae02207e42a8325a4dd41702bf065aa6e0a1b7b0b8ee92a5e6c182da018b0afc82c40601".to_string()])]
pub witness: Vec<String>,
/// Whether this input is a coinbase (block reward) input
#[schemars(example = false)]
pub is_coinbase: bool,
/// Input sequence number
#[schemars(example = 429496729)]
pub sequence: u32,
/// Inner redeemscript in assembly format (for P2SH-wrapped SegWit)
#[schemars(example = Some("OP_0 OP_PUSHBYTES_20 992a1f7420fc5285070d19c71ff2efb1e356ad2f".to_string()))]
pub inner_redeemscript_asm: Option<String>,
}
+102
View File
@@ -0,0 +1,102 @@
use crate::{Address, AddressBytes, OutputType, Sats};
use bitcoin::ScriptBuf;
use bitcoincore_rpc::json::GetTxOutResult;
use schemars::JsonSchema;
use serde::{Serialize, Serializer, ser::SerializeStruct};
#[derive(Debug, JsonSchema)]
/// Transaction output
pub struct TxOut {
/// Script pubkey (locking script)
#[serde(
rename = "scriptpubkey",
serialize_with = "serialize_with_script_pubkey"
)]
#[schemars(
with = "String",
example = "00143b064c595a95f977f00352d6e917501267cacdc6"
)]
pub script_pubkey: ScriptBuf,
/// Script pubkey in assembly format
#[allow(dead_code)]
#[serde(skip, rename = "scriptpubkey_asm")]
#[schemars(
with = "String",
example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6"
)]
script_pubkey_asm: (),
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, etc.)
#[allow(dead_code)]
#[serde(skip, rename = "scriptpubkey_type")]
#[schemars(with = "OutputType", example = &"v0_p2wpkh")]
script_pubkey_type: (),
/// Bitcoin address (if applicable, None for OP_RETURN)
#[allow(dead_code)]
#[serde(skip, rename = "scriptpubkey_address")]
#[schemars(with = "Option<Address>", example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
script_pubkey_address: (),
/// Value of the output in satoshis
#[schemars(example = Sats::new(7782))]
pub value: Sats,
}
impl TxOut {
pub fn address(&self) -> Option<Address> {
Address::try_from(&self.script_pubkey).ok()
}
pub fn address_bytes(&self) -> Option<AddressBytes> {
AddressBytes::try_from(&self.script_pubkey).ok()
}
pub fn type_(&self) -> OutputType {
OutputType::from(&self.script_pubkey)
}
pub fn script_pubkey_asm(&self) -> String {
self.script_pubkey.to_asm_string()
}
}
impl From<bitcoin::TxOut> for TxOut {
fn from(txout: bitcoin::TxOut) -> Self {
Self {
script_pubkey: txout.script_pubkey,
value: txout.value.into(),
script_pubkey_asm: (),
script_pubkey_address: (),
script_pubkey_type: (),
}
}
}
impl From<GetTxOutResult> for TxOut {
fn from(value: GetTxOutResult) -> Self {
Self {
script_pubkey: value.script_pub_key.script().unwrap(),
script_pubkey_address: (),
script_pubkey_asm: (),
script_pubkey_type: (),
value: value.value.into(),
}
}
}
impl Serialize for TxOut {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("TxOut", 5)?;
state.serialize_field("scriptpubkey", &self.script_pubkey.to_hex_string())?;
state.serialize_field("scriptpubkey_asm", &self.script_pubkey_asm())?;
state.serialize_field("scriptpubkey_type", &self.type_())?;
state.serialize_field("scriptpubkey_address", &self.address())?;
state.serialize_field("value", &self.value)?;
state.end()
}
}
-27
View File
@@ -1,27 +0,0 @@
use crate::Sats;
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Transaction output
pub struct TxOutput {
/// Script pubkey (locking script)
#[schemars(example = "00143b064c595a95f977f00352d6e917501267cacdc6")]
pub scriptpubkey: String,
/// Script pubkey in assembly format
#[schemars(example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6")]
pub scriptpubkey_asm: String,
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, etc.)
#[schemars(example = &"v0_p2wpkh")]
pub scriptpubkey_type: String,
/// Bitcoin address (if applicable, None for OP_RETURN)
#[schemars(example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
pub scriptpubkey_address: Option<String>,
/// Value of the output in satoshis
#[schemars(example = Sats::new(7782))]
pub value: Sats,
}
-27
View File
@@ -1,27 +0,0 @@
use crate::Sats;
use schemars::JsonSchema;
use serde::Serialize;
#[derive(Debug, Serialize, JsonSchema)]
/// Information about a previous transaction output being spent
pub struct TxPrevout {
/// Script pubkey (locking script)
#[schemars(example = "00143b064c595a95f977f00352d6e917501267cacdc6")]
pub scriptpubkey: String,
/// Script pubkey in assembly format
#[schemars(example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6")]
pub scriptpubkey_asm: String,
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
#[schemars(example = &"v0_p2wpkh")]
pub scriptpubkey_type: String,
/// Bitcoin address (if applicable)
#[schemars(example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
pub scriptpubkey_address: Option<String>,
/// Value of the output in satoshis
#[schemars(example = Sats::new(7813))]
pub value: Sats,
}
+9
View File
@@ -22,3 +22,12 @@ pub struct TxStatus {
#[schemars(example = Some(1759000868))]
pub block_time: Option<Timestamp>,
}
impl TxStatus {
pub const UNCOMFIRMED: Self = Self {
confirmed: false,
block_hash: None,
block_height: None,
block_time: None,
};
}
+2
View File
@@ -2,6 +2,7 @@ use std::ops::{Add, AddAssign, Div};
use allocative::Allocative;
use derive_deref::Deref;
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::StoredCompressed;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
@@ -22,6 +23,7 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
Serialize,
StoredCompressed,
Allocative,
JsonSchema,
)]
pub struct Weight(u64);