use anyhow::Context;
use bitcoin::{consensus::Decodable, io::Cursor};
use lexe_serde::hexstr_or_bytes;
use serde::{Deserialize, Serialize};
use super::user::NodePk;
#[cfg(any(test, feature = "test-utils"))]
use crate::test_utils::arbitrary;
use crate::{
ln::{amount::Amount, hashes::Txid, network::Network},
time::TimestampMs,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Status {
pub timestamp: TimestampMs,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SignMsgRequest {
pub msg: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SignMsgResponse {
pub sig: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VerifyMsgRequest {
pub msg: String,
pub sig: String,
pub pk: NodePk,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VerifyMsgResponse {
pub is_valid: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BroadcastedTx {
pub txid: Txid,
#[serde(with = "hexstr_or_bytes")]
pub tx: Vec<u8>,
pub created_at: TimestampMs,
}
impl BroadcastedTx {
pub fn new(txid: Txid, tx: Vec<u8>) -> Self {
Self {
txid,
tx,
created_at: TimestampMs::now(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BroadcastedTxInfo {
pub txid: Txid,
#[serde(with = "hexstr_or_bytes")]
pub tx: Vec<u8>,
pub created_at: TimestampMs,
pub total_outputs: Amount,
pub output_destinations: Vec<String>,
pub inputs: Vec<String>,
pub confirmation_block_height: Option<u32>,
}
impl BroadcastedTxInfo {
pub fn from_broadcasted_tx(
broadcasted_tx: BroadcastedTx,
network: Network,
confirmation_block_height: Option<u32>,
) -> anyhow::Result<Self> {
let mut reader = Cursor::new(&broadcasted_tx.tx);
let tx = bitcoin::Transaction::consensus_decode(&mut reader)
.context("Could not parse consensus-encoded transaction")?;
let total_outputs =
tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
let total_outputs = Amount::try_from_sats_u64(total_outputs)
.context("Output amount conversion error")?;
let output_destinations = tx
.output
.iter()
.map(|o| {
bitcoin::Address::from_script(
&o.script_pubkey,
network.to_bitcoin(),
)
.map_or(o.script_pubkey.to_string(), |addr| addr.to_string())
})
.collect::<Vec<_>>();
let inputs = tx
.input
.iter()
.map(|i| i.previous_output.to_string())
.collect::<Vec<_>>();
let tx_info = BroadcastedTxInfo {
total_outputs,
created_at: broadcasted_tx.created_at,
output_destinations,
inputs,
txid: broadcasted_tx.txid,
tx: broadcasted_tx.tx,
confirmation_block_height,
};
Ok(tx_info)
}
}
#[cfg(any(test, feature = "test-utils"))]
mod arbitrary_impl {
use proptest::{
arbitrary::{Arbitrary, any},
collection::vec,
option,
strategy::{BoxedStrategy, Strategy},
};
use super::*;
impl Arbitrary for BroadcastedTx {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
arbitrary::any_raw_tx_bytes(),
any::<Txid>(),
any::<TimestampMs>(),
)
.prop_map(|(tx, txid, created_at)| Self {
tx,
txid,
created_at,
})
.boxed()
}
}
impl Arbitrary for BroadcastedTxInfo {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let any_vec_input_str = vec(
(arbitrary::any_outpoint())
.prop_map(|outpoint| outpoint.to_string()),
0..=8,
);
let any_vec_output_destination_str = vec(
(arbitrary::any_mainnet_addr())
.prop_map(|address| address.to_string()),
0..=8,
);
let any_confirmation_block_height_optional =
option::of(any::<u32>());
(
any::<BroadcastedTx>(),
any::<Amount>(),
any_vec_output_destination_str,
any_vec_input_str,
any_confirmation_block_height_optional,
)
.prop_map(
|(
tx,
total_outputs,
output_destinations,
inputs,
confirmation_block_height,
)| Self {
txid: tx.txid,
tx: tx.tx,
created_at: tx.created_at,
total_outputs,
output_destinations,
inputs,
confirmation_block_height,
},
)
.boxed()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::roundtrip;
#[test]
fn broadcasted_tx_roundtrip_proptest() {
roundtrip::json_value_roundtrip_proptest::<BroadcastedTx>();
}
#[test]
fn broadcasted_tx_info_roundtrip_proptest() {
roundtrip::json_value_roundtrip_proptest::<BroadcastedTxInfo>();
}
}