diff --git a/Cargo.lock b/Cargo.lock index f05e9b624e..2107069007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1562,6 +1562,7 @@ dependencies = [ "secp256k1 0.27.0", "serde", "serde_json", + "serde_path_to_error", "sha2 0.10.8", "sp-core 21.0.0 (git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+3)", "sp-rpc", @@ -10612,6 +10613,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.4" diff --git a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/btc_mempool.rs b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/btc_mempool.rs index 134b6bca52..77322b8301 100644 --- a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/btc_mempool.rs +++ b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/btc_mempool.rs @@ -123,20 +123,20 @@ async fn get_updated_cache(btc: &T, previous_cache: Cache) -> anyh for confirmations in 1..SAFETY_MARGIN { let block = btc.block(block_hash_to_query).await?; for tx in block.txdata { - let tx_hash = tx.txid(); - for txout in tx.output { + let tx_hash = tx.txid; + for txout in tx.vout { new_transactions.insert( txout.script_pubkey.clone(), QueryResult { destination: txout.script_pubkey, confirmations, - value: Amount::from_sat(txout.value).to_btc(), + value: txout.value.to_btc(), tx_hash, }, ); } } - block_hash_to_query = block.header.prev_blockhash; + block_hash_to_query = block.header.previous_block_hash.unwrap(); } } Ok(Cache { @@ -224,14 +224,17 @@ mod tests { use std::collections::BTreeMap; use bitcoin::{ - absolute::LockTime, + absolute::{Height, LockTime}, address::{self}, - block::{Header, Version}, + block::Version, hash_types::TxMerkleNode, hashes::Hash, - Block, TxOut, + secp256k1::rand::{self, Rng}, + TxOut, + }; + use chainflip_engine::btc::rpc::{ + BlockHeader, Difficulty, VerboseBlock, VerboseTransaction, VerboseTxOut, }; - use chainflip_engine::btc::rpc::BlockHeader; use super::*; @@ -239,12 +242,12 @@ mod tests { struct MockBtcRpc { mempool: Vec, latest_block_hash: BlockHash, - blocks: BTreeMap, + blocks: BTreeMap, } #[async_trait::async_trait] impl BtcRpcApi for MockBtcRpc { - async fn block(&self, block_hash: BlockHash) -> anyhow::Result { + async fn block(&self, block_hash: BlockHash) -> anyhow::Result { self.blocks.get(&block_hash).cloned().ok_or(anyhow!("Block missing")) } async fn best_block_hash(&self) -> anyhow::Result { @@ -300,45 +303,92 @@ mod tests { BlockHash::from_byte_array([i; 32]) } - fn header_with_prev_hash(i: u8) -> Header { - Header { + fn header_with_prev_hash(i: u8) -> BlockHeader { + let hash = i_to_block_hash(i + 1); + BlockHeader { version: Version::from_consensus(0), - prev_blockhash: i_to_block_hash(i), + previous_block_hash: Some(i_to_block_hash(i)), merkle_root: TxMerkleNode::from_byte_array([0u8; 32]), time: 0, bits: Default::default(), nonce: 0, + hash, + confirmations: 1, + height: 2000, + version_hex: Default::default(), + median_time: Default::default(), + difficulty: Difficulty::Number(1.0), + chainwork: Default::default(), + n_tx: Default::default(), + next_block_hash: None, + strippedsize: None, + size: None, + weight: None, } } - fn init_blocks() -> BTreeMap { - let mut blocks: BTreeMap = Default::default(); + fn init_blocks() -> BTreeMap { + let mut blocks: BTreeMap = Default::default(); for i in 1..16 { blocks.insert( i_to_block_hash(i), - Block { header: header_with_prev_hash(i - 1), txdata: vec![] }, + VerboseBlock { header: header_with_prev_hash(i - 1), txdata: vec![] }, ); } blocks } + pub fn verbose_transaction( + tx_outs: Vec, + fee: Option, + ) -> VerboseTransaction { + let random_number: u8 = rand::thread_rng().gen(); + let txid = Txid::from_byte_array([random_number; 32]); + VerboseTransaction { + txid, + version: Version::from_consensus(2), + locktime: LockTime::Blocks(Height::from_consensus(0).unwrap()), + vin: vec![], + vout: tx_outs, + fee, + // not important, we just need to set it to a value. + hash: txid, + size: Default::default(), + vsize: Default::default(), + weight: Default::default(), + hex: Default::default(), + } + } + + pub fn verbose_vouts(vals_and_scripts: Vec<(u64, ScriptBuf)>) -> Vec { + vals_and_scripts + .into_iter() + .enumerate() + .map(|(n, (value, script_pub_key))| VerboseTxOut { + value: Amount::from_sat(value), + n: n as u64, + script_pubkey: script_pub_key, + }) + .collect() + } + // This creates one tx out in one transaction for each item in txdata - fn block_prev_hash_tx_outs(i: u8, txdata: Vec<(Amount, String)>) -> Block { - Block { + fn block_prev_hash_tx_outs(i: u8, txdata: Vec<(Amount, String)>) -> VerboseBlock { + VerboseBlock { header: header_with_prev_hash(i), txdata: txdata .into_iter() - .map(|(value, destination)| bitcoin::Transaction { - output: vec![TxOut { - value: value.to_sat(), - script_pubkey: bitcoin::Address::from_str(&destination) - .unwrap() - .payload - .script_pubkey(), - }], - input: Default::default(), - version: 0, - lock_time: LockTime::from_consensus(0), + .map(|(value, destination)| { + verbose_transaction( + verbose_vouts(vec![( + value.to_sat(), + bitcoin::Address::from_str(&destination) + .unwrap() + .payload + .script_pubkey(), + )]), + None, + ) }) .collect(), } @@ -440,7 +490,7 @@ mod tests { let mempool = vec![]; - let mut blocks: BTreeMap = Default::default(); + let mut blocks: BTreeMap = Default::default(); for i in 1..19 { blocks.insert(i_to_block_hash(i), block_prev_hash_tx_outs(i - 1, vec![])); } diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 160332e2c2..06534b475f 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -151,6 +151,7 @@ tempfile = "3.7.0" utilities = { package = "utilities", path = "../utilities", features = [ "test-utils", ] } +serde_path_to_error = "*" [build-dependencies] substrate-build-script-utils = { git = "https://github.com/chainflip-io/substrate.git", tag = "chainflip-monthly-2023-08+3" } diff --git a/engine/src/btc/retry_rpc.rs b/engine/src/btc/retry_rpc.rs index dc630c26b7..c3c38bc40c 100644 --- a/engine/src/btc/retry_rpc.rs +++ b/engine/src/btc/retry_rpc.rs @@ -1,4 +1,4 @@ -use bitcoin::{Block, BlockHash, Txid}; +use bitcoin::{BlockHash, Txid}; use utilities::task_scope::Scope; use crate::{ @@ -11,7 +11,7 @@ use core::time::Duration; use anyhow::Result; -use super::rpc::{BlockHeader, BtcRpcApi, BtcRpcClient}; +use super::rpc::{BlockHeader, BtcRpcApi, BtcRpcClient, VerboseBlock}; #[derive(Clone)] pub struct BtcRetryRpcClient { @@ -51,7 +51,7 @@ impl BtcRetryRpcClient { #[async_trait::async_trait] pub trait BtcRetryRpcApi: Clone { - async fn block(&self, block_hash: BlockHash) -> Block; + async fn block(&self, block_hash: BlockHash) -> VerboseBlock; async fn block_hash(&self, block_number: cf_chains::btc::BlockNumber) -> BlockHash; @@ -66,7 +66,7 @@ pub trait BtcRetryRpcApi: Clone { #[async_trait::async_trait] impl BtcRetryRpcApi for BtcRetryRpcClient { - async fn block(&self, block_hash: BlockHash) -> Block { + async fn block(&self, block_hash: BlockHash) -> VerboseBlock { self.retry_client .request( Box::pin(move |client| { @@ -201,7 +201,7 @@ pub mod mocks { #[async_trait::async_trait] impl BtcRetryRpcApi for BtcRetryRpcClient { - async fn block(&self, block_hash: BlockHash) -> Block; + async fn block(&self, block_hash: BlockHash) -> VerboseBlock; async fn block_hash(&self, block_number: cf_chains::btc::BlockNumber) -> BlockHash; diff --git a/engine/src/btc/rpc.rs b/engine/src/btc/rpc.rs index 9ef230df28..181160d080 100644 --- a/engine/src/btc/rpc.rs +++ b/engine/src/btc/rpc.rs @@ -3,12 +3,14 @@ use futures_core::Future; use thiserror::Error; use reqwest::Client; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use serde; -use serde_json::json; +use serde_json::{json, Map}; -use bitcoin::{block::Version, Amount, Block, BlockHash, Transaction, Txid}; +use bitcoin::{ + absolute, block::Version, Amount, BlockHash, ScriptBuf, Sequence, Transaction, Txid, +}; use tracing::error; use utilities::make_periodic_tick; @@ -208,6 +210,89 @@ async fn get_bitcoin_network( BitcoinNetwork::try_from(network_name) } +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum VerboseOutPoint { + #[serde(rename = "coinbase")] + Coinbase { coinbase: String }, + #[serde(rename = "txid")] + Txid { txid: Txid, vout: u32 }, +} + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct VerboseTxIn { + #[serde(flatten)] + pub outpoint: VerboseOutPoint, + pub txinwitness: Option>, + pub sequence: Sequence, +} + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct VerboseTxOut { + #[serde(with = "bitcoin::amount::serde::as_btc")] + pub value: Amount, + pub n: u64, + #[serde(rename = "scriptPubKey")] + #[serde(deserialize_with = "deserialize_scriptpubkey")] + pub script_pubkey: ScriptBuf, +} + +fn deserialize_scriptpubkey<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Helper { + hex: String, + } + let helper = Helper::deserialize(deserializer)?; + Ok(ScriptBuf::from(hex::decode(helper.hex).map_err(serde::de::Error::custom)?)) +} + +// This is a work around for this bug, it is effectively a catch all. +// https://github.com/serde-rs/json/issues/721 +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Difficulty { + Number(f64), + Object(Map), +} + +/// Transaction type when the verbose flag is used. +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VerboseTransaction { + pub txid: Txid, + pub hash: Txid, + pub version: Version, + pub size: usize, + pub vsize: usize, + pub weight: usize, + pub locktime: absolute::LockTime, + pub vin: Vec, + pub vout: Vec, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "bitcoin::amount::serde::as_btc::opt" + )] + pub fee: Option, + pub hex: String, +} + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct VerboseBlock { + /// The block header + #[serde(flatten)] + pub header: BlockHeader, + + /// List of transactions contained in the block\ + #[serde(rename = "tx")] + pub txdata: Vec, +} + #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BlockHeader { @@ -215,9 +300,8 @@ pub struct BlockHeader { pub confirmations: i32, pub height: u64, pub version: Version, + pub version_hex: Option, // Don't care to write custom deserializer for this - #[serde(skip)] - pub version_hex: Option>, #[serde(rename = "merkleroot")] pub merkle_root: bitcoin::hash_types::TxMerkleNode, pub time: usize, @@ -225,20 +309,23 @@ pub struct BlockHeader { pub median_time: Option, pub nonce: u32, pub bits: String, - pub difficulty: f64, + pub difficulty: Difficulty, // Don't care to write custom deserializer for this - #[serde(skip)] - pub chainwork: Vec, + pub chainwork: Option, pub n_tx: usize, #[serde(rename = "previousblockhash")] pub previous_block_hash: Option, #[serde(rename = "nextblockhash")] pub next_block_hash: Option, + + pub strippedsize: Option, + pub size: Option, + pub weight: Option, } #[async_trait::async_trait] pub trait BtcRpcApi { - async fn block(&self, block_hash: BlockHash) -> anyhow::Result; + async fn block(&self, block_hash: BlockHash) -> anyhow::Result; async fn block_hash( &self, @@ -265,18 +352,13 @@ pub trait BtcRpcApi { #[async_trait::async_trait] impl BtcRpcApi for BtcRpcClient { - async fn block(&self, block_hash: BlockHash) -> anyhow::Result { - // The 0 arg means we get the response as a hex string, which we use in the custom - // deserialization. - let hex_block: Vec = self - .call_rpc("getblock", ReqParams::Batch(vec![json!([json!(block_hash), json!(0)])])) - .await?; - let hex_block = hex_block + async fn block(&self, block_hash: BlockHash) -> anyhow::Result { + Ok(self + .call_rpc("getblock", ReqParams::Batch(vec![json!([json!(block_hash), json!(2)])])) + .await? .into_iter() .next() - .ok_or_else(|| anyhow!("Response missing hex block"))?; - let hex_bytes = hex::decode(hex_block).context("Response not valid hex")?; - Ok(bitcoin::consensus::encode::deserialize(&hex_bytes)?) + .ok_or_else(|| anyhow!("Response missing block"))?) } async fn block_hash( @@ -429,11 +511,25 @@ mod tests { basic_auth_user: "flip".to_string(), basic_auth_password: "flip".to_string(), }, - Some(BitcoinNetwork::Regtest), + None, ) .unwrap() .await; + // from a `getblock` RPC call with verbosity 2 + let block_data: &str = r#"{"hash":"7f03b5690dffca7e446fa986df8c2269389e5846b4e5e8942e8230238392cc55","confirmations":235,"height":113,"version":536870912,"versionHex":"20000000","merkleroot":"3eb919e3d8be10620dd18daa33578859fd4ee8fcfa7d182ad33ef5b69e9347aa","time":1702309098,"mediantime":1702309073,"nonce":0,"bits":"207fffff","difficulty":4.656542373906925e-10,"chainwork":"00000000000000000000000000000000000000000000000000000000000000e4","nTx":2,"previousblockhash":"524d0847614eb89dffd4291786c111473fe7ca3334c7fddd2fa399481a2a99e7","nextblockhash":"7f3bf60a2dc7f2726b7afd5b7fc04d8626edd174779e16c6a3dfa6a450758156","strippedsize":332,"size":477,"weight":1473,"tx":[{"txid":"e2056652441becbfe33cc3b51c70a804360bdf1a730e65f0ee9abbe4028bf8f2","hash":"596da34ae4bb652af9872990ffe6746b922cee46e5fee25f8a16b9e6c3d17c73","version":2,"size":168,"vsize":141,"weight":564,"locktime":0,"vin":[{"coinbase":"017100","txinwitness":["0000000000000000000000000000000000000000000000000000000000000000"],"sequence":4294967295}],"vout":[{"value":50.00000147,"n":0,"scriptPubKey":{"asm":"0 a66802f0279cc06c04abe451733d0644b7cd1aa3","desc":"addr(bcrt1q5e5q9up8nnqxcp9tu3ghx0gxgjmu6x4rkhnr04)#qqa77u3g","hex":"0014a66802f0279cc06c04abe451733d0644b7cd1aa3","address":"bcrt1q5e5q9up8nnqxcp9tu3ghx0gxgjmu6x4rkhnr04","type":"witness_v0_keyhash"}},{"value":0.00000000,"n":1,"scriptPubKey":{"asm":"OP_RETURN aa21a9ed9b0c92763f407d12a24735fbdb9e720367e70b0117030e8bc648a5c03ebd4a5d","desc":"raw(6a24aa21a9ed9b0c92763f407d12a24735fbdb9e720367e70b0117030e8bc648a5c03ebd4a5d)#8sr8jfxw","hex":"6a24aa21a9ed9b0c92763f407d12a24735fbdb9e720367e70b0117030e8bc648a5c03ebd4a5d","type":"nulldata"}}],"hex":"020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03017100ffffffff0293f2052a01000000160014a66802f0279cc06c04abe451733d0644b7cd1aa30000000000000000266a24aa21a9ed9b0c92763f407d12a24735fbdb9e720367e70b0117030e8bc648a5c03ebd4a5d0120000000000000000000000000000000000000000000000000000000000000000000000000"},{"txid":"7b43fc408a6b1eb61f312b6732c27a2dde77828de7a677157d1ea03f7888748a","hash":"47d0b049a25be39a6f5b7871d9c6f9350e945095ba09aa3290494f399a4c66f7","version":2,"size":228,"vsize":147,"weight":585,"locktime":112,"vin":[{"txid":"773171b0c840f1dc69c247db64efc553dc2ce0c18558cd9ecef8e7bf81a81f2d","vout":0,"scriptSig":{"asm":"","hex":""},"txinwitness":["30440220379b67fd1ac79d3416aad215b9185144430a71c5f530cafcc076fe4df3025c75022045f823e4d2f15a5daa89ee1682a15f42b7cd95f6594b34315589676aab92203901","03b22186e3b2c239cb47066fca1f42b416772a27a113ba2c968bba069354f7c20a"],"sequence":4294967293}],"vout":[{"value":39.99999853,"n":0,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 0b308fdc0ae8b11dde0673387ac36f20100bf5e9 OP_EQUALVERIFY OP_CHECKSIG","desc":"addr(mgY7uJoK6HszJqYxcwAKpJFj56bEJiuAhB)#pu2vn08p","hex":"76a9140b308fdc0ae8b11dde0673387ac36f20100bf5e988ac","address":"mgY7uJoK6HszJqYxcwAKpJFj56bEJiuAhB","type":"pubkeyhash"}},{"value":10.00000000,"n":1,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 9a1c78a507689f6f54b847ad1cef1e614ee23f1e OP_EQUALVERIFY OP_CHECKSIG","desc":"addr(muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi)#t9q9hu3x","hex":"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","address":"muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi","type":"pubkeyhash"}}],"fee":0.00000147,"hex":"020000000001012d1fa881bfe7f8ce9ecd5885c1e02cdc53c5ef64db47c269dcf140c8b07131770000000000fdffffff026d276bee000000001976a9140b308fdc0ae8b11dde0673387ac36f20100bf5e988ac00ca9a3b000000001976a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac024730440220379b67fd1ac79d3416aad215b9185144430a71c5f530cafcc076fe4df3025c75022045f823e4d2f15a5daa89ee1682a15f42b7cd95f6594b34315589676aab922039012103b22186e3b2c239cb47066fca1f42b416772a27a113ba2c968bba069354f7c20a70000000"}]}"#; + let jd = &mut serde_json::Deserializer::from_str(block_data); + + let result: Result = serde_path_to_error::deserialize(jd); + + match result { + Ok(vb) => { + println!("vb: {vb:?}"); + println!("Verbose block fee: {}", vb.txdata[1].fee.unwrap()); + }, + Err(e) => panic!("error: {e:?}"), + } + let block_hash_zero = client.block_hash(0).await.unwrap(); println!("block_hash_zero: {block_hash_zero:?}"); @@ -454,9 +550,21 @@ mod tests { println!("block_header: {block_header:?}"); - let average_block_fee_rate = client.average_block_fee_rate(best_block_hash).await.unwrap(); + let v_block = client.block(best_block_hash).await.unwrap(); + + println!("verbose block: {v_block:?}"); + + println!("number of txs: {}", v_block.txdata.len()); + + let tx = &v_block.txdata[0]; + + let raw_transaction = client.get_raw_transactions(vec![tx.txid]).await.unwrap()[0].txid(); + assert_eq!(raw_transaction, tx.txid); + + // let average_block_fee_rate = + // client.average_block_fee_rate(best_block_hash).await.unwrap(); - println!("average_block_fee_rate: {average_block_fee_rate}"); + // println!("average_block_fee_rate: {average_block_fee_rate}"); // Generate new hex bytes using ./bouncer/commands/create_raw_btc_tx.ts; // let hex_str = diff --git a/engine/src/witness/btc.rs b/engine/src/witness/btc.rs index ffa5511595..2319320758 100644 --- a/engine/src/witness/btc.rs +++ b/engine/src/witness/btc.rs @@ -4,7 +4,7 @@ pub mod btc_source; use std::sync::Arc; -use bitcoin::{BlockHash, Transaction}; +use bitcoin::BlockHash; use cf_chains::btc::{self, deposit_address::DepositAddress, BlockNumber, CHANGE_ADDRESS_SALT}; use cf_primitives::{EpochIndex, NetworkEnvironment}; use futures_core::Future; @@ -12,7 +12,10 @@ use secp256k1::hashes::Hash; use utilities::task_scope::Scope; use crate::{ - btc::retry_rpc::{BtcRetryRpcApi, BtcRetryRpcClient}, + btc::{ + retry_rpc::{BtcRetryRpcApi, BtcRetryRpcClient}, + rpc::VerboseTransaction, + }, db::PersistentKeyDB, state_chain_observer::client::{ extrinsic_api::signed::SignedExtrinsicApi, storage_api::StorageApi, StateChainStreamApi, @@ -29,7 +32,7 @@ use anyhow::Result; pub async fn process_egress( epoch: Vault, - header: Header, Vec<(btc::Hash, BlockNumber)>)>, + header: Header, Vec<(btc::Hash, BlockNumber)>)>, process_call: ProcessCall, ) where ProcessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> ProcessingFut @@ -43,7 +46,7 @@ pub async fn process_egress( monitored_tx_hashes: impl Iterator + Clone, - txs: &Vec, -) -> Vec { + txs: Vec, +) -> Vec<(btc::Hash, VerboseTransaction)> { let mut successful_witnesses = Vec::new(); for tx in txs { let mut monitored = monitored_tx_hashes.clone(); - let tx_hash = tx.txid().as_raw_hash().to_byte_array(); + let tx_hash = tx.txid.as_raw_hash().to_byte_array(); + if monitored.any(|&monitored_hash| monitored_hash == tx_hash) { - successful_witnesses.push(tx_hash); + successful_witnesses.push((tx_hash, tx)); } } successful_witnesses @@ -196,49 +200,47 @@ fn success_witnesses<'a>( #[cfg(test)] mod tests { - use super::*; - use bitcoin::{ - absolute::{Height, LockTime}, - ScriptBuf, Transaction, TxOut, - }; + use bitcoin::Amount; - fn fake_transaction(tx_outs: Vec) -> Transaction { - Transaction { - version: 2, - lock_time: LockTime::Blocks(Height::from_consensus(0).unwrap()), - input: vec![], - output: tx_outs, - } - } + use super::*; + use crate::witness::btc::btc_deposits::tests::{fake_transaction, fake_verbose_vouts}; #[test] fn witnesses_tx_hash_successfully() { + const FEE_0: u64 = 1; + const FEE_1: u64 = 111; + const FEE_2: u64 = 222; + const FEE_3: u64 = 333; let txs = vec![ - fake_transaction(vec![]), - fake_transaction(vec![TxOut { - value: 2324, - script_pubkey: ScriptBuf::from(vec![0, 32, 121, 9]), - }]), - fake_transaction(vec![TxOut { - value: 232232, - script_pubkey: ScriptBuf::from(vec![32, 32, 121, 9]), - }]), - fake_transaction(vec![TxOut { - value: 232232, - script_pubkey: ScriptBuf::from(vec![33, 2, 1, 9]), - }]), + fake_transaction(vec![], Some(Amount::from_sat(FEE_0))), + fake_transaction( + fake_verbose_vouts(vec![(2324, vec![0, 32, 121, 9])]), + Some(Amount::from_sat(FEE_1)), + ), + fake_transaction( + fake_verbose_vouts(vec![(232232, vec![32, 32, 121, 9])]), + Some(Amount::from_sat(FEE_2)), + ), + fake_transaction( + fake_verbose_vouts(vec![(232232, vec![32, 32, 121, 9])]), + Some(Amount::from_sat(FEE_3)), + ), ]; let tx_hashes = - txs.iter().map(|tx| tx.txid().to_raw_hash().to_byte_array()).collect::>(); + txs.iter().map(|tx| tx.txid.to_raw_hash().to_byte_array()).collect::>(); // we're not monitoring for index 2, and they're out of order. - let mut monitored_hashes = vec![tx_hashes[3], tx_hashes[0], tx_hashes[1]]; + let monitored_hashes = [tx_hashes[3], tx_hashes[0], tx_hashes[1]]; - let mut success_witnesses = success_witnesses(monitored_hashes.iter(), &txs); - success_witnesses.sort(); - monitored_hashes.sort(); + let sorted_monitored_hashes = vec![tx_hashes[0], tx_hashes[1], tx_hashes[3]]; - assert_eq!(success_witnesses, monitored_hashes); + let (success_witness_hashes, txs): (Vec<_>, Vec<_>) = + success_witnesses(monitored_hashes.iter(), txs).into_iter().unzip(); + assert_eq!(sorted_monitored_hashes, success_witness_hashes); + assert_eq!(txs[0].fee.unwrap().to_sat(), FEE_0); + assert_eq!(txs[1].fee.unwrap().to_sat(), FEE_1); + // we weren't monitoring for 2, so the last fee should be FEE_3. + assert_eq!(txs[2].fee.unwrap().to_sat(), FEE_3); } } diff --git a/engine/src/witness/btc/btc_deposits.rs b/engine/src/witness/btc/btc_deposits.rs index 16abbdcdfd..732f601941 100644 --- a/engine/src/witness/btc/btc_deposits.rs +++ b/engine/src/witness/btc/btc_deposits.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use bitcoin::Transaction; use cf_primitives::EpochIndex; use futures_core::Future; use itertools::Itertools; @@ -11,9 +10,12 @@ use state_chain_runtime::BitcoinInstance; use super::super::common::chunked_chain_source::chunked_by_vault::{ builder::ChunkedByVaultBuilder, ChunkedByVault, }; -use crate::witness::common::{ - chunked_chain_source::chunked_by_vault::deposit_addresses::Addresses, RuntimeCallHasChain, - RuntimeHasChain, +use crate::{ + btc::rpc::VerboseTransaction, + witness::common::{ + chunked_chain_source::chunked_by_vault::deposit_addresses::Addresses, RuntimeCallHasChain, + RuntimeHasChain, + }, }; use bitcoin::BlockHash; use cf_chains::{ @@ -27,13 +29,18 @@ impl ChunkedByVaultBuilder { self, process_call: ProcessCall, ) -> ChunkedByVaultBuilder< - impl ChunkedByVault, Chain = Bitcoin>, + impl ChunkedByVault< + Index = u64, + Hash = BlockHash, + Data = Vec, + Chain = Bitcoin, + >, > where Inner: ChunkedByVault< Index = u64, Hash = BlockHash, - Data = (((), Vec), Addresses), + Data = (((), Vec), Addresses), Chain = Bitcoin, >, ProcessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> ProcessingFut @@ -75,23 +82,23 @@ impl ChunkedByVaultBuilder { } fn deposit_witnesses( - txs: &Vec, + txs: &Vec, script_addresses: HashMap, ScriptPubkey>, ) -> Vec> { let mut deposit_witnesses = Vec::new(); for tx in txs { - let tx_hash = tx.txid().as_raw_hash().to_byte_array(); + let tx_hash = tx.txid.as_raw_hash().to_byte_array(); let deposits_in_tx = (0..) - .zip(&tx.output) - .filter(|(_vout, tx_out)| tx_out.value > 0) + .zip(&tx.vout) + .filter(|(_vout, tx_out)| tx_out.value.to_sat() > 0) .filter_map(|(vout, tx_out)| { let tx_script_pubkey_bytes = tx_out.script_pubkey.to_bytes(); script_addresses.get(&tx_script_pubkey_bytes).map( |bitcoin_script| DepositWitness:: { deposit_address: bitcoin_script.clone(), asset: btc::Asset::Btc, - amount: tx_out.value, + amount: tx_out.value.to_sat(), deposit_details: UtxoId { tx_id: tx_hash, vout }, }, ) @@ -125,26 +132,40 @@ fn script_addresses( } #[cfg(test)] -mod tests { +pub mod tests { + + use crate::btc::rpc::VerboseTxOut; use super::*; use bitcoin::{ absolute::{Height, LockTime}, - ScriptBuf, Transaction, TxOut, + block::Version, + Amount, ScriptBuf, Txid, }; use cf_chains::{ btc::{deposit_address::DepositAddress, ScriptPubkey}, DepositChannel, }; use pallet_cf_ingress_egress::ChannelAction; + use rand::Rng; use sp_runtime::AccountId32; - fn fake_transaction(tx_outs: Vec) -> Transaction { - Transaction { - version: 2, - lock_time: LockTime::Blocks(Height::from_consensus(0).unwrap()), - input: vec![], - output: tx_outs, + pub fn fake_transaction(tx_outs: Vec, fee: Option) -> VerboseTransaction { + let random_number: u8 = rand::thread_rng().gen(); + let txid = Txid::from_byte_array([random_number; 32]); + VerboseTransaction { + txid, + version: Version::from_consensus(2), + locktime: LockTime::Blocks(Height::from_consensus(0).unwrap()), + vin: vec![], + vout: tx_outs, + fee, + // not important, we just need to set it to a value. + hash: txid, + size: Default::default(), + vsize: Default::default(), + weight: Default::default(), + hex: Default::default(), } } @@ -166,9 +187,21 @@ mod tests { } } + pub fn fake_verbose_vouts(vals_and_scripts: Vec<(u64, Vec)>) -> Vec { + vals_and_scripts + .into_iter() + .enumerate() + .map(|(n, (value, script_bytes))| VerboseTxOut { + value: Amount::from_sat(value), + n: n as u64, + script_pubkey: ScriptBuf::from(script_bytes), + }) + .collect() + } + #[test] fn deposit_witnesses_no_utxos_no_monitored() { - let txs = vec![fake_transaction(vec![]), fake_transaction(vec![])]; + let txs = vec![fake_transaction(vec![], None), fake_transaction(vec![], None)]; let deposit_witnesses = deposit_witnesses(&txs, HashMap::new()); assert!(deposit_witnesses.is_empty()); } @@ -178,10 +211,13 @@ mod tests { let btc_deposit_script: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey(); const UTXO_WITNESSED_1: u64 = 2324; - let txs = vec![fake_transaction(vec![ - TxOut { value: 2324, script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()) }, - TxOut { value: 0, script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()) }, - ])]; + let txs = vec![fake_transaction( + fake_verbose_vouts(vec![ + (2324, btc_deposit_script.bytes()), + (0, btc_deposit_script.bytes()), + ]), + None, + )]; let deposit_witnesses = deposit_witnesses(&txs, script_addresses(vec![(fake_details(btc_deposit_script))])); @@ -198,22 +234,16 @@ mod tests { let btc_deposit_script: ScriptPubkey = DepositAddress::new([0; 32], 9).script_pubkey(); let txs = vec![ - fake_transaction(vec![ - TxOut { - value: UTXO_TO_DEPOSIT_2, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - TxOut { value: 12223, script_pubkey: ScriptBuf::from(vec![0, 32, 121, 9]) }, - TxOut { - value: LARGEST_UTXO_TO_DEPOSIT, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - TxOut { - value: UTXO_TO_DEPOSIT_3, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - ]), - fake_transaction(vec![]), + fake_transaction( + fake_verbose_vouts(vec![ + (UTXO_TO_DEPOSIT_2, btc_deposit_script.bytes()), + (12223, vec![0, 32, 121, 9]), + (LARGEST_UTXO_TO_DEPOSIT, btc_deposit_script.bytes()), + (UTXO_TO_DEPOSIT_3, btc_deposit_script.bytes()), + ]), + None, + ), + fake_transaction(vec![], None), ]; let deposit_witnesses = @@ -232,22 +262,16 @@ mod tests { let btc_deposit_script_2: ScriptPubkey = DepositAddress::new([0; 32], 1232).script_pubkey(); let txs = vec![ - fake_transaction(vec![ - TxOut { - value: UTXO_TO_DEPOSIT_2, - script_pubkey: ScriptBuf::from(btc_deposit_script_1.bytes()), - }, - TxOut { value: 12223, script_pubkey: ScriptBuf::from(vec![0, 32, 121, 9]) }, - TxOut { - value: LARGEST_UTXO_TO_DEPOSIT, - script_pubkey: ScriptBuf::from(btc_deposit_script_1.bytes()), - }, - TxOut { - value: UTXO_FOR_SECOND_DEPOSIT, - script_pubkey: ScriptBuf::from(btc_deposit_script_2.bytes()), - }, - ]), - fake_transaction(vec![]), + fake_transaction( + fake_verbose_vouts(vec![ + (UTXO_TO_DEPOSIT_2, btc_deposit_script_1.bytes()), + (12223, vec![0, 32, 121, 9]), + (LARGEST_UTXO_TO_DEPOSIT, btc_deposit_script_1.bytes()), + (UTXO_FOR_SECOND_DEPOSIT, btc_deposit_script_2.bytes()), + ]), + None, + ), + fake_transaction(vec![], None), ]; let mut deposit_witnesses = deposit_witnesses( @@ -275,29 +299,21 @@ mod tests { const UTXO_WITNESSED_1: u64 = 2324; const UTXO_WITNESSED_2: u64 = 1234; let txs = vec![ - fake_transaction(vec![ - TxOut { - value: UTXO_WITNESSED_1, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - TxOut { value: 12223, script_pubkey: ScriptBuf::from(vec![0, 32, 121, 9]) }, - TxOut { - // smaller value, so we will only take the largest one - value: UTXO_WITNESSED_1 - 1, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - ]), - fake_transaction(vec![ - TxOut { - // smaller, so we only take the largest (different order to above) - value: UTXO_WITNESSED_2 - 10, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - TxOut { - value: UTXO_WITNESSED_2, - script_pubkey: ScriptBuf::from(btc_deposit_script.bytes()), - }, - ]), + fake_transaction( + fake_verbose_vouts(vec![ + (UTXO_WITNESSED_1, btc_deposit_script.bytes()), + (12223, vec![0, 32, 121, 9]), + (UTXO_WITNESSED_1 - 1, btc_deposit_script.bytes()), + ]), + None, + ), + fake_transaction( + fake_verbose_vouts(vec![ + (UTXO_WITNESSED_2 - 10, btc_deposit_script.bytes()), + (UTXO_WITNESSED_2, btc_deposit_script.bytes()), + ]), + None, + ), ]; let deposit_witnesses =