From b5fe75a5be1fdf326ef056814b3da92e13a9f5e5 Mon Sep 17 00:00:00 2001 From: meryacine Date: Wed, 22 Jun 2022 22:11:06 +0200 Subject: [PATCH 001/119] Bump up some deps Needed to bump the lightning version used and had to bump bitcoin, bitcoincore-rpc aswell to match dep verions in lightining merkle root computation changed in `bitcoin`, thus some methods in the test_utils needed to adapt (basically by adding a tx if there is none in a block). See: https://github.com/rust-bitcoin/rust-bitcoin/commit/b454cf8e159b78b10e6c47a03e77c1e59171f71c Also SecretKey had it's `to_string` method removed, so TEOS now encodes its tower key using `display_secret().to_string()`. See: https://github.com/rust-bitcoin/rust-secp256k1/pull/312 --- teos-common/Cargo.toml | 6 +++--- teos-common/src/appointment.rs | 2 -- teos/Cargo.toml | 10 +++++----- teos/src/bitcoin_cli.rs | 21 +++++++++------------ teos/src/chain_monitor.rs | 4 ++++ teos/src/dbm.rs | 16 ++++++++++++++-- teos/src/gatekeeper.rs | 4 ++++ teos/src/responder.rs | 8 ++++++++ teos/src/test_utils.rs | 32 +++++++++++++++++++------------- teos/src/watcher.rs | 4 ++++ watchtower-plugin/Cargo.toml | 2 +- watchtower-plugin/src/dbm.rs | 16 ++++++++++++++-- 12 files changed, 85 insertions(+), 40 deletions(-) diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 75feb0f..0d9eab7 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -20,8 +20,8 @@ rand = "0.8.4" chacha20poly1305 = "0.8.0" # Bitcoin and Lightning -bitcoin = { version = "0.27", features = [ "use-serde" ] } -lightning = "0.0.105" +bitcoin = { version = "0.28.0", features = [ "use-serde" ] } +lightning = "0.0.108" [build-dependencies] -tonic-build = "0.6" \ No newline at end of file +tonic-build = "0.6" diff --git a/teos-common/src/appointment.rs b/teos-common/src/appointment.rs index 880646e..983b6a1 100644 --- a/teos-common/src/appointment.rs +++ b/teos-common/src/appointment.rs @@ -1,8 +1,6 @@ //! Logic related to appointments shared between users and the towers. -use hex; use serde::{Deserialize, Serialize}; - use std::array::TryFromSliceError; use std::{convert::TryInto, fmt}; diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 17416fe..c03291a 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -34,11 +34,11 @@ warp = "0.3.2" torut = "0.2.1" # Bitcoin and Lightning -bitcoin = { version = "0.27", features = [ "base64" ] } -bitcoincore-rpc = "0.14.0" -lightning = "0.0.105" -lightning-net-tokio = "0.0.105" -lightning-block-sync = { version = "0.0.105", features = [ "rpc-client" ] } +bitcoin = { version = "0.28.0", features = [ "base64" ] } +bitcoincore-rpc = "0.15.0" +lightning = "0.0.108" +lightning-net-tokio = "0.0.108" +lightning-block-sync = { version = "0.0.108", features = [ "rpc-client" ] } # Local teos-common = { path = "../teos-common" } diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 6d66c28..8f10ea5 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -38,31 +38,28 @@ pub struct BitcoindClient<'a> { impl BlockSource for &BitcoindClient<'_> { /// Gets a block header given its hash. fn get_header<'a>( - &'a mut self, + &'a self, header_hash: &'a BlockHash, height_hint: Option, ) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; rpc.get_header(header_hash, height_hint).await }) } /// Gets a block given its hash. - fn get_block<'a>( - &'a mut self, - header_hash: &'a BlockHash, - ) -> AsyncBlockSourceResult<'a, Block> { + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> { Box::pin(async move { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; rpc.get_block(header_hash).await }) } /// Get the best block known by our node. - fn get_best_block(&mut self) -> AsyncBlockSourceResult<(BlockHash, Option)> { + fn get_best_block(&self) -> AsyncBlockSourceResult<(BlockHash, Option)> { Box::pin(async move { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; rpc.get_best_block().await }) } @@ -108,14 +105,14 @@ impl<'a> BitcoindClient<'a> { pub async fn get_best_block_hash_and_height( &self, ) -> Result<(BlockHash, Option), std::io::Error> { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; rpc.call_method::<(BlockHash, Option)>("getblockchaininfo", &[]) .await } /// Sends a transaction to the network. pub async fn send_raw_transaction(&self, raw_tx: &Transaction) -> Result { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; let raw_tx_json = serde_json::json!(raw_tx.encode().to_hex()); rpc.call_method::("sendrawtransaction", &[raw_tx_json]) @@ -124,7 +121,7 @@ impl<'a> BitcoindClient<'a> { /// Gets a transaction given its id. pub async fn get_raw_transaction(&self, txid: &Txid) -> Result { - let mut rpc = self.bitcoind_rpc_client.lock().await; + let rpc = self.bitcoind_rpc_client.lock().await; let txid_hex = serde_json::json!(txid.encode().to_hex()); rpc.call_method::("getrawtransaction", &[txid_hex]) diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 9c9f2e6..fb7225c 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -167,6 +167,10 @@ mod tests { .borrow_mut() .insert(header.block_hash()); } + + fn filtered_block_connected(&self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { + + } } #[tokio::test] diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index fe6b9a0..8b43500 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -542,7 +542,7 @@ impl DBM { /// When a new key is generated, old keys are not overwritten but are not retrievable from the API either. pub fn store_tower_key(&self, sk: &SecretKey) -> Result<(), Error> { let query = "INSERT INTO keys (key) VALUES (?)"; - self.store_data(query, params![sk.to_string()]) + self.store_data(query, params![sk.display_secret().to_string()]) } /// Loads the last known tower secret key from the database. @@ -570,7 +570,7 @@ mod tests { use super::*; use std::iter::FromIterator; - use teos_common::cryptography::get_random_bytes; + use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::test_utils::get_random_user_id; use crate::test_utils::{ @@ -1237,4 +1237,16 @@ mod tests { assert!(matches!(dbm.load_last_known_block(), Err(Error::NotFound))); } + + #[test] + fn test_store_load_tower_key() { + let dbm = DBM::in_memory().unwrap(); + + assert!(matches!(dbm.load_tower_key(), Err(Error::NotFound))); + for _ in 0..7 { + let sk = get_random_keypair().0; + dbm.store_tower_key(&sk).unwrap(); + assert_eq!(dbm.load_tower_key().unwrap(), sk); + } + } } diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index d1e3764..17452dc 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -335,6 +335,10 @@ impl chain::Listen for Gatekeeper { self.last_known_block_height .store(height - 1, Ordering::Release); } + + fn filtered_block_connected(&self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { + + } } #[cfg(test)] diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 0a7a41f..8d26236 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -560,6 +560,14 @@ impl chain::Listen for Responder { } } } + + fn filtered_block_connected( + &self, + header: &BlockHeader, + txdata: &chain::transaction::TransactionData, + height: u32, + ) { + } } #[cfg(test)] diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 5ecd833..df4684f 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -28,6 +28,7 @@ use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::util::hash::bitcoin_merkle_root; use bitcoin::util::uint::Uint256; +use bitcoin::Witness; use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, }; @@ -86,16 +87,18 @@ impl Blockchain { let prev_block = &self.blocks[i - 1]; let prev_blockhash = prev_block.block_hash(); let time = prev_block.header.time + height as u32; + let txdata = vec![get_random_tx()]; + let hashes = txdata.iter().map(|obj| obj.txid().as_hash()); self.blocks.push(Block { header: BlockHeader { version: 0, prev_blockhash, - merkle_root: Default::default(), + merkle_root: bitcoin_merkle_root(hashes).unwrap().into(), time, bits, nonce: 0, }, - txdata: vec![], + txdata, }); } self @@ -200,14 +203,20 @@ impl Blockchain { let prev_blockhash = prev_block.block_hash(); let time = prev_block.header.time + (self.blocks.len() + 1) as u32; let txdata = match txs { - Some(t) => t, - None => vec![], + Some(v) => { + if v.is_empty() { + vec![get_random_tx()] + } else { + v + } + } + None => vec![get_random_tx()], }; let hashes = txdata.iter().map(|obj| obj.txid().as_hash()); let mut header = BlockHeader { version: 0, prev_blockhash, - merkle_root: bitcoin_merkle_root(hashes).into(), + merkle_root: bitcoin_merkle_root(hashes).unwrap().into(), time, bits, nonce: 0, @@ -226,7 +235,7 @@ impl Blockchain { impl BlockSource for Blockchain { fn get_header<'a>( - &'a mut self, + &'a self, header_hash: &'a BlockHash, _height_hint: Option, ) -> AsyncBlockSourceResult<'a, BlockHeaderData> { @@ -249,10 +258,7 @@ impl BlockSource for Blockchain { }) } - fn get_block<'a>( - &'a mut self, - header_hash: &'a BlockHash, - ) -> AsyncBlockSourceResult<'a, Block> { + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> { Box::pin(async move { for (height, block) in self.blocks.iter().enumerate() { if block.header.block_hash() == *header_hash { @@ -269,7 +275,7 @@ impl BlockSource for Blockchain { }) } - fn get_best_block(&mut self) -> AsyncBlockSourceResult<(BlockHash, Option)> { + fn get_best_block(&self) -> AsyncBlockSourceResult<(BlockHash, Option)> { Box::pin(async move { if *self.unreachable.lock().unwrap() { return Err(BlockSourceError::transient("Connection refused")); @@ -304,7 +310,7 @@ pub(crate) fn get_random_tx() -> Transaction { rng.gen_range(0..200), ), script_sig: Script::new(), - witness: Vec::new(), + witness: Witness::new(), sequence: 0, }], output: vec![TxOut { @@ -363,7 +369,7 @@ pub(crate) fn store_appointment_and_fks_to_db( pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec { let tip = chain.tip(); - let mut poller = ChainPoller::new(chain, Network::Bitcoin); + let poller = ChainPoller::new(chain, Network::Bitcoin); let mut last_n_blocks = Vec::new(); let mut last_known_block = tip; diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 0b3a63c..f319084 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -877,6 +877,10 @@ impl chain::Listen for Watcher { self.last_known_block_height .store(height - 1, Ordering::Release); } + + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { + + } } #[cfg(test)] diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index e1db23e..fcf85f8 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -25,7 +25,7 @@ tonic = { version = "^0.5", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning -bitcoin = "0.27" +bitcoin = "0.28.0" cln-plugin = "0.1.0" # Local diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index e2e9d03..89578be 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -118,7 +118,7 @@ impl DBM { /// When a new key is generated, old keys are not overwritten but are not retrievable from the API either. pub fn store_client_key(&self, sk: &SecretKey) -> Result<(), Error> { let query = "INSERT INTO keys (key) VALUES (?)"; - self.store_data(query, params![sk.to_string()]) + self.store_data(query, params![sk.display_secret().to_string()]) } /// Loads the last known client secret key from the database. @@ -132,7 +132,6 @@ impl DBM { "SELECT key FROM keys WHERE id = (SELECT seq FROM sqlite_sequence WHERE name=(?))", ) .unwrap(); - stmt.query_row(["keys"], |row| { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) @@ -600,6 +599,7 @@ impl DBM { mod tests { use super::*; + use teos_common::cryptography::get_random_keypair; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, get_registration_receipt_from_previous, @@ -1177,4 +1177,16 @@ mod tests { let dbm = DBM::in_memory().unwrap(); assert!(!dbm.exists_misbehaving_proof(get_random_user_id())); } + + #[test] + fn test_store_load_client_key() { + let dbm = DBM::in_memory().unwrap(); + + assert!(matches!(dbm.load_client_key(), Err(Error::NotFound))); + for _ in 0..7 { + let sk = get_random_keypair().0; + dbm.store_client_key(&sk).unwrap(); + assert_eq!(dbm.load_client_key().unwrap(), sk); + } + } } From 50356fee97c2902dbe16f7462ef00972d93d0157 Mon Sep 17 00:00:00 2001 From: meryacine Date: Sun, 26 Jun 2022 18:35:38 +0200 Subject: [PATCH 002/119] Renaming `block_connected` to `filtered_block_connected` Implement `filtered_block_connected` and rely on `block_connected` default implementation that calls `filtered_block_connected` --- teos-common/Cargo.toml | 2 +- teos/src/chain_monitor.rs | 13 +++++++------ teos/src/gatekeeper.rs | 15 ++++++++------- teos/src/responder.rs | 27 ++++++++++----------------- teos/src/watcher.rs | 25 +++++++++++++------------ watchtower-plugin/src/dbm.rs | 1 + 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 0d9eab7..18bfabc 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -24,4 +24,4 @@ bitcoin = { version = "0.28.0", features = [ "use-serde" ] } lightning = "0.0.108" [build-dependencies] -tonic-build = "0.6" +tonic-build = "0.6" \ No newline at end of file diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index fb7225c..4314a51 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -156,10 +156,15 @@ mod tests { } impl chain::Listen for DummyListener { - fn block_connected(&self, block: &bitcoin::Block, _: u32) { + fn filtered_block_connected( + &self, + header: &bitcoin::BlockHeader, + _: &chain::transaction::TransactionData, + _: u32, + ) { self.connected_blocks .borrow_mut() - .insert(block.block_hash()); + .insert(header.block_hash()); } fn block_disconnected(&self, header: &bitcoin::BlockHeader, _: u32) { @@ -167,10 +172,6 @@ mod tests { .borrow_mut() .insert(header.block_hash()); } - - fn filtered_block_connected(&self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { - - } } #[tokio::test] diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 17452dc..8441e03 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -312,8 +312,13 @@ impl chain::Listen for Gatekeeper { /// Handles the monitoring process by the [Gatekeeper]. /// /// This is mainly used to keep track of time and expire / outdate subscriptions when needed. - fn block_connected(&self, block: &bitcoin::Block, height: u32) { - log::info!("New block received: {}", block.block_hash()); + fn filtered_block_connected( + &self, + header: &bitcoin::BlockHeader, + _: &chain::transaction::TransactionData, + height: u32, + ) { + log::info!("New block received: {}", header.block_hash()); // Expired user deletion is delayed. Users are deleted when their subscription is outdated, not expired. let outdated_users = self.get_outdated_user_ids(height); @@ -335,10 +340,6 @@ impl chain::Listen for Gatekeeper { self.last_known_block_height .store(height - 1, Ordering::Release); } - - fn filtered_block_connected(&self, header: &bitcoin::BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { - - } } #[cfg(test)] @@ -821,7 +822,7 @@ mod tests { } #[test] - fn test_block_connected() { + fn test_filtered_block_connected() { // block_connected in the Gatekeeper is used to keep track of time in order to manage the users' subscription expiry. // Remove users that get outdated at the new block's height from registered_users and the database. let mut chain = Blockchain::default().with_height(START_HEIGHT); diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 8d26236..0e81b74 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -492,18 +492,19 @@ impl chain::Listen for Responder { /// Every time a block is received the tracking conditions are checked against the monitored [TransactionTracker]s and /// data deletion is performed accordingly. Moreover, lack of confirmations is check for the tracked transactions and /// rebroadcasting is performed for those that have missed too many. - fn block_connected(&self, block: &bitcoin::Block, height: u32) { - log::info!("New block received: {}", block.header.block_hash()); + fn filtered_block_connected( + &self, + header: &BlockHeader, + txdata: &chain::transaction::TransactionData, + height: u32, + ) { + log::info!("New block received: {}", header.block_hash()); self.carrier.lock().unwrap().update_height(height); if self.trackers.lock().unwrap().len() > 0 { // Complete those appointments that are due at this height let completed_trackers = self.check_confirmations( - &block - .txdata - .iter() - .map(|tx| tx.txid()) - .collect::>(), + &txdata.iter().map(|(_, tx)| tx.txid()).collect::>(), height, ); let trackers_to_delete_gk = completed_trackers @@ -560,14 +561,6 @@ impl chain::Listen for Responder { } } } - - fn filtered_block_connected( - &self, - header: &BlockHeader, - txdata: &chain::transaction::TransactionData, - height: u32, - ) { - } } #[cfg(test)] @@ -1485,7 +1478,7 @@ mod tests { .unwrap(); // Delete trackers removes data from the trackers, tx_tracker_map maps, the database. The deletion of the later is - // better check in test_block_connected. Add data to the map first. + // better check in test_filtered_block_connected. Add data to the map first. let mut all_trackers = HashSet::new(); let mut target_trackers = HashSet::new(); let mut uuid_txid_map = HashMap::new(); @@ -1605,7 +1598,7 @@ mod tests { } #[test] - fn test_block_connected() { + fn test_filtered_block_connected() { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let start_height = START_HEIGHT * 2; let mut chain = Blockchain::default().with_height(start_height); diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index f319084..750289b 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex}; use bitcoin::hash_types::BlockHash; use bitcoin::secp256k1::SecretKey; -use bitcoin::{Block, BlockHeader, Transaction}; +use bitcoin::{BlockHeader, Transaction}; use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -796,19 +796,23 @@ impl chain::Listen for Watcher { /// /// This also takes care of updating the [LocatorCache] and removing outdated data from the [Watcher] when /// told by the [Gatekeeper]. - fn block_connected(&self, block: &Block, height: u32) { - log::info!("New block received: {}", block.header.block_hash()); + fn filtered_block_connected( + &self, + header: &BlockHeader, + txdata: &chain::transaction::TransactionData, + height: u32, + ) { + log::info!("New block received: {}", header.block_hash()); - let locator_tx_map = block - .txdata + let locator_tx_map = txdata .iter() - .map(|tx| (Locator::new(tx.txid()), tx.clone())) + .map(|(_, tx)| (Locator::new(tx.txid()), (*tx).clone())) .collect(); self.locator_cache .lock() .unwrap() - .update(block.header, &locator_tx_map); + .update(*header, &locator_tx_map); if !self.appointments.lock().unwrap().is_empty() { // Start by removing outdated data so it is not taken into account from this point on @@ -877,10 +881,6 @@ impl chain::Listen for Watcher { self.last_known_block_height .store(height - 1, Ordering::Release); } - - fn filtered_block_connected(&self, header: &BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { - - } } #[cfg(test)] @@ -905,6 +905,7 @@ mod tests { use bitcoin::hash_types::Txid; use bitcoin::hashes::Hash; use bitcoin::secp256k1::{PublicKey, Secp256k1}; + use bitcoin::Block; use lightning::chain::Listen; impl PartialEq for Watcher { @@ -1883,7 +1884,7 @@ mod tests { } #[tokio::test] - async fn test_block_connected() { + async fn test_filtered_block_connected() { let mut chain = Blockchain::default().with_height(START_HEIGHT); let watcher = init_watcher(&mut chain).await; diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 89578be..4a0c647 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -132,6 +132,7 @@ impl DBM { "SELECT key FROM keys WHERE id = (SELECT seq FROM sqlite_sequence WHERE name=(?))", ) .unwrap(); + stmt.query_row(["keys"], |row| { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) From 2c91e94e7007417d722d4b4e3aa72fee79fc5160 Mon Sep 17 00:00:00 2001 From: booklearner Date: Fri, 22 Jul 2022 11:11:07 -0400 Subject: [PATCH 003/119] change `rename_all` macro value to `snake_case` --- teos/src/cli_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/cli_config.rs b/teos/src/cli_config.rs index bdfbdee..4f93720 100644 --- a/teos/src/cli_config.rs +++ b/teos/src/cli_config.rs @@ -21,7 +21,7 @@ pub enum Command { } #[derive(Debug, StructOpt, Clone)] -#[structopt(rename_all = "lowercase")] +#[structopt(rename_all = "snake_case")] pub struct GetUserData { /// The user identifier (33-byte compressed public key). pub user_id: String, From 3d816cc8afd04074fefefeff2433c94a8a43ee10 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 16 Aug 2022 10:51:16 +0200 Subject: [PATCH 004/119] Disable clippy::derive_partial_eq_without_eq in protos Clippy for Rust 1.63.0 raises a lint warning regarding structures implementing PartialEq but not Eq: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq The autogenerated code from the protos does fall into this constrain. However, the current recommended solution from the `prost` team is to disable the check: https://github.com/tokio-rs/prost/issues/661 --- teos-common/src/lib.rs | 2 ++ teos/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/teos-common/src/lib.rs b/teos-common/src/lib.rs index afd853d..a4d5cc5 100644 --- a/teos-common/src/lib.rs +++ b/teos-common/src/lib.rs @@ -2,6 +2,8 @@ //! //! Functionality shared between users and towers. +// FIXME: This is a temporary fix. See https://github.com/tokio-rs/prost/issues/661 +#[allow(clippy::derive_partial_eq_without_eq)] pub mod protos { tonic::include_proto!("common.teos.v2"); } diff --git a/teos/src/lib.rs b/teos/src/lib.rs index ce4ac6d..e1073ea 100644 --- a/teos/src/lib.rs +++ b/teos/src/lib.rs @@ -2,6 +2,8 @@ //! //! A watchtower implementation written in Rust. +// FIXME: This is a temporary fix. See https://github.com/tokio-rs/prost/issues/661 +#[allow(clippy::derive_partial_eq_without_eq)] pub mod protos { tonic::include_proto!("teos.v2"); } From 7e7dc973f5d2a0ce285a17868ca54104ae85abb2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 16 Jun 2022 17:21:05 +0200 Subject: [PATCH 005/119] Fixes Tracker serialization for get_all_appointments close #64 --- teos-common/build.rs | 3 +++ teos-common/src/ser.rs | 45 +++++++++++++++++++++++++++++++-- watchtower-plugin/tests/test.py | 33 ++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/teos-common/build.rs b/teos-common/build.rs index c132feb..52c390f 100644 --- a/teos-common/build.rs +++ b/teos-common/build.rs @@ -7,6 +7,9 @@ fn main() -> Result<(), Box> { .field_attribute("user_id", "#[serde(with = \"hex::serde\")]") .field_attribute("locator", "#[serde(with = \"hex::serde\")]") .field_attribute("encrypted_blob", "#[serde(with = \"hex::serde\")]") + .field_attribute("dispute_txid", "#[serde(with = \"crate::ser::serde_be\")]") + .field_attribute("penalty_txid", "#[serde(with = \"crate::ser::serde_be\")]") + .field_attribute("penalty_rawtx", "#[serde(with = \"hex::serde\")]") .field_attribute( "GetAppointmentResponse.status", "#[serde(with = \"crate::ser::serde_status\")]", diff --git a/teos-common/src/ser.rs b/teos-common/src/ser.rs index 84879d2..663ff66 100644 --- a/teos-common/src/ser.rs +++ b/teos-common/src/ser.rs @@ -15,9 +15,50 @@ where seq.end() } -pub mod serde_status { +pub mod serde_be { + use super::*; + use serde::de::{self, Deserializer}; + + pub fn serialize(v: &[u8], s: S) -> Result + where + S: Serializer, + { + let mut v = v.to_owned(); + v.reverse(); + hex::serialize(v, s) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct BEVisitor; + + impl<'de> de::Visitor<'de> for BEVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex encoded string") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let mut v = + hex::decode(v).map_err(|_| E::custom("cannot deserialize the given value"))?; + v.reverse(); + Ok(v) + } + } + + deserializer.deserialize_any(BEVisitor) + } +} + +pub mod serde_status { + use super::*; use serde::de::{self, Deserializer}; - use serde::ser::Serializer; use std::str::FromStr; use crate::appointment::AppointmentStatus; diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 8e5030b..521be61 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -156,3 +156,36 @@ def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) assert l2.rpc.gettowerinfo(tower_id)["status"] == "misbehaving" assert l2.rpc.gettowerinfo(tower_id)["misbehaving_proof"] + + +def test_get_appointment(node_factory, bitcoind, teosd, directory): + l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) + + # We need to register l2 with the tower + tower_id = teosd.cli.get_tower_info()["tower_id"] + l2.rpc.registertower(tower_id) + + # Force a new commitment + l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) + tx = l1.rpc.dev_sign_last_tx(l2.info["id"])["tx"] + + # Now make sure it is out of date + l1.rpc.pay(l2.rpc.invoice(25000000, "lbl2", "desc2")["bolt11"]) + + # Now l1 cheats + dispute_txid = bitcoind.rpc.sendrawtransaction(tx) + locator = change_endianness(dispute_txid[32:]) + + # Check the appointment before mining a block + appointment = l2.rpc.getappointment(tower_id, locator)["appointment"] + assert "locator" in appointment and "encrypted_blob" in appointment and "to_self_delay" in appointment + + bitcoind.generate_block(1) + time.sleep(1) + + # And after. Now this should be a tracker + tracker = l2.rpc.getappointment(tower_id, locator)["appointment"] + assert "dispute_txid" in tracker and "penalty_txid" in tracker and "penalty_rawtx" in tracker + + # Manually stop l2, otherwise the tower may be stopped before the tower client and we may get some BROKEN logs. + l2.stop() From d4a293feb2d92edb48523acc68138452269f85e3 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 8 Aug 2022 17:20:00 +0200 Subject: [PATCH 006/119] Adds getsubscriptioninfo to the CLN plugin --- teos-common/build.rs | 4 +++ teos-common/src/ser.rs | 48 +++++++++++++++++++++++++++++++++++ teos/build.rs | 4 +-- teos/src/api/http.rs | 13 +--------- watchtower-plugin/README.md | 3 ++- watchtower-plugin/src/main.rs | 47 ++++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 15 deletions(-) diff --git a/teos-common/build.rs b/teos-common/build.rs index 52c390f..a66e76f 100644 --- a/teos-common/build.rs +++ b/teos-common/build.rs @@ -6,6 +6,10 @@ fn main() -> Result<(), Box> { .field_attribute("appointment_data", "#[serde(rename = \"appointment\")]") .field_attribute("user_id", "#[serde(with = \"hex::serde\")]") .field_attribute("locator", "#[serde(with = \"hex::serde\")]") + .field_attribute( + "locators", + "#[serde(with = \"crate::ser::serde_vec_bytes\")]", + ) .field_attribute("encrypted_blob", "#[serde(with = \"hex::serde\")]") .field_attribute("dispute_txid", "#[serde(with = \"crate::ser::serde_be\")]") .field_attribute("penalty_txid", "#[serde(with = \"crate::ser::serde_be\")]") diff --git a/teos-common/src/ser.rs b/teos-common/src/ser.rs index 663ff66..4946ba3 100644 --- a/teos-common/src/ser.rs +++ b/teos-common/src/ser.rs @@ -56,6 +56,54 @@ pub mod serde_be { } } +pub mod serde_vec_bytes { + use super::*; + use serde::de::{self, Deserializer, SeqAccess}; + + pub fn serialize(v: &[Vec], s: S) -> Result + where + S: Serializer, + { + let mut seq = s.serialize_seq(Some(v.len()))?; + for element in v.iter() { + seq.serialize_element(&hex::encode(element))?; + } + seq.end() + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + struct VecVisitor; + + impl<'de> de::Visitor<'de> for VecVisitor { + type Value = Vec>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex encoded string") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut result = Vec::new(); + while let Some(v) = seq.next_element::()? { + result + .push(hex::decode(v).map_err(|_| { + de::Error::custom("cannot deserialize the given value") + })?); + } + + Ok(result) + } + } + + deserializer.deserialize_any(VecVisitor) + } +} + pub mod serde_status { use super::*; use serde::de::{self, Deserializer}; diff --git a/teos/build.rs b/teos/build.rs index 56482d4..0bd1477 100644 --- a/teos/build.rs +++ b/teos/build.rs @@ -6,11 +6,11 @@ fn main() -> Result<(), Box> { .field_attribute("tower_id", "#[serde(with = \"hex::serde\")]") .field_attribute( "user_ids", - "#[serde(serialize_with = \"crate::api::http::serialize_vec_bytes\")]", + "#[serde(serialize_with = \"teos_common::ser::serde_vec_bytes::serialize\")]", ) .field_attribute( "GetUserResponse.appointments", - "#[serde(serialize_with = \"crate::api::http::serialize_vec_bytes\")]", + "#[serde(serialize_with = \"teos_common::ser::serde_vec_bytes::serialize\")]", ) .compile( &[ diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 177f8c6..644de14 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -1,4 +1,4 @@ -use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use std::convert::Infallible; use std::error::Error; use std::net::SocketAddr; @@ -58,17 +58,6 @@ impl ApiError { } } -pub fn serialize_vec_bytes(v: &[Vec], s: S) -> Result -where - S: Serializer, -{ - let mut seq = s.serialize_seq(Some(v.len()))?; - for element in v.iter() { - seq.serialize_element(&hex::encode(element))?; - } - seq.end() -} - fn with_grpc( grpc_endpoint: PublicTowerServicesClient, ) -> impl Filter,), Error = Infallible> + Clone { diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index 815a1a7..9afe8ab 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -9,8 +9,9 @@ commitment transaction is generated. It also keeps a summary of the messages sen The plugin has the following methods: - `registertower tower_id` : registers the user id (compressed public key) with a given tower. -- `list_towers`: lists all registered towers. +- `listtowers`: lists all registered towers. - `gettowerinfo tower_id`: gets all the locally stored data about a given tower. +- `getsubscriptioninfo tower_id`: gets the subscription information by querying the tower. - `retrytower tower_id`: tries to send pending appointment to a (previously) unreachable tower. - `getappointment tower_id locator`: queries a given tower about an appointment. diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index b4d14e5..c5f3a16 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -180,6 +180,48 @@ async fn get_appointment( Ok(json!(response)) } +/// Gets the subscription information directly form the tower. +async fn get_subscription_info( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; + + let user_sk = plugin.state().lock().unwrap().user_sk; + let tower_net_addr = { + let state = plugin.state().lock().unwrap(); + if let Some(info) = state.towers.get(&tower_id) { + Ok(info.net_addr.clone()) + } else { + Err(anyhow!("Unknown tower id: {}", tower_id)) + } + }?; + + let get_subscription_info = format!("{}/get_subscription_info", tower_net_addr); + let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk).unwrap(); + + let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( + post_request( + &get_subscription_info, + &common_msgs::GetSubscriptionInfoRequest { signature }, + ) + .await, + ) + .await + .map_err(|e| { + if e.is_connection() { + plugin + .state() + .lock() + .unwrap() + .set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); + } + to_cln_error(e) + })?; + + Ok(json!(response)) +} + /// Lists all the registered towers. /// /// The given information comes from memory, so it is summarized. @@ -408,6 +450,11 @@ async fn main() -> Result<(), Error> { "Gets appointment data from the tower given the tower id and the locator.", get_appointment, ) + .rpcmethod( + "getsubscriptioninfo", + "Gets the subscription information directly from the tower.", + get_subscription_info, + ) .rpcmethod("listtowers", "Lists all registered towers.", list_towers) .rpcmethod( "gettowerinfo", From 33148c16baa76bc72f95d65a0beae87f4a1f7df2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 1 Jul 2022 16:14:45 +0200 Subject: [PATCH 007/119] Adds some extra commands to the cln-plugin - `abandontower ` will remove all data associated to a given tower - `getappointmentreceipt ` will pull a given appointment receipt from the local database. - `getregistrationreceipt ` will pull a given registration receipt from the local database. --- watchtower-plugin/README.md | 13 +- watchtower-plugin/src/dbm.rs | 123 +++++++++++++++++- watchtower-plugin/src/main.rs | 146 ++++++++++++++++----- watchtower-plugin/src/retrier.rs | 104 +++++++++++++-- watchtower-plugin/src/wt_client.rs | 197 +++++++++++++++++++++++++++++ 5 files changed, 529 insertions(+), 54 deletions(-) diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index 9afe8ab..e3ecdac 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -8,12 +8,15 @@ commitment transaction is generated. It also keeps a summary of the messages sen The plugin has the following methods: -- `registertower tower_id` : registers the user id (compressed public key) with a given tower. +- `registertower `: registers the user id (compressed public key) with a given tower. +- `gettowerinfo `: gets all the locally stored data about a given tower. +- `retrytower `: tries to send pending appointment to a (previously) unreachable tower. +- `abandontower `: deletes all data associated with a given tower. - `listtowers`: lists all registered towers. -- `gettowerinfo tower_id`: gets all the locally stored data about a given tower. -- `getsubscriptioninfo tower_id`: gets the subscription information by querying the tower. -- `retrytower tower_id`: tries to send pending appointment to a (previously) unreachable tower. -- `getappointment tower_id locator`: queries a given tower about an appointment. +- `getappointment `: queries a given tower about an appointment. +- `getsubscriptioninfo `: gets the subscription information by querying the tower. +- `getappointmentreceipt `: pulls a given appointment receipt from the local database. +- `getregistrationreceipt `: pulls the latest registration receipt from the local database. The plugin also has an implicit method to send appointments to the registered towers for every new commitment transaction. diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 4a0c647..a59458c 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -244,6 +244,15 @@ impl DBM { Ok(receipt) } + /// Removes a tower record from the database. + /// + /// This triggers a cascade deletion of all related data, such as appointments, appointment receipts, etc. As long as there is a single + /// reference to them. + pub fn remove_tower_record(&self, tower_id: TowerId) -> Result<(), Error> { + let query = "DELETE FROM towers WHERE tower_id=?"; + self.remove_data(query, params![tower_id.to_vec()]) + } + /// Loads all tower records from the database. pub fn load_towers(&self) -> HashMap { let mut towers = HashMap::new(); @@ -316,7 +325,32 @@ impl DBM { tx.commit() } - /// Loads the appointment receipts associated to a given tower + /// Loads a given appointment receipt of a given tower from the database. + pub fn load_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Result { + let mut stmt = self + .connection + .prepare("SELECT * FROM appointment_receipts WHERE tower_id = ?1 and locator = ?2") + .unwrap(); + + stmt.query_row(params![tower_id.to_vec(), locator.to_vec()], |row| { + let start_block = row.get::<_, u32>(2).unwrap(); + let user_sig = row.get::<_, String>(3).unwrap(); + let tower_sig = row.get::<_, String>(4).unwrap(); + + Ok(AppointmentReceipt::with_signature( + user_sig, + start_block, + tower_sig, + )) + }) + .map_err(|_| Error::NotFound) + } + + /// Loads the appointment receipts associated to a given tower. /// /// TODO: Currently this is only loading a summary of the receipt, if we need to really load all the information /// for any reason this method may need to be renamed. @@ -623,6 +657,19 @@ mod tests { .unwrap(); stmt.exists(params![locator.to_vec()]).unwrap() } + + pub(crate) fn appointment_receipt_exists( + &self, + locator: Locator, + tower_id: TowerId, + ) -> bool { + let mut stmt = self + .connection + .prepare("SELECT * FROM appointment_receipts WHERE locator=?1 AND tower_id=?2 ") + .unwrap(); + stmt.exists(params![locator.to_vec(), tower_id.to_vec()]) + .unwrap() + } } #[test] @@ -776,6 +823,29 @@ mod tests { assert_eq!(dbm.load_towers(), HashMap::new()); } + #[test] + fn test_remove_tower_record() { + let mut dbm = DBM::in_memory().unwrap(); + + let tower_id = get_random_user_id(); + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + assert!(matches!(dbm.remove_tower_record(tower_id), Ok(()))); + } + + #[test] + fn test_remove_tower_record_inexistent() { + let dbm = DBM::in_memory().unwrap(); + + assert!(matches!( + dbm.remove_tower_record(get_random_user_id()), + Err(Error::NotFound) + )); + } + #[test] fn test_store_load_appointment_receipts() { let mut dbm = DBM::in_memory().unwrap(); @@ -823,6 +893,57 @@ mod tests { assert_eq!(dbm.load_appointment_receipts(tower_id), receipts); } + #[test] + fn test_load_appointment_receipt() { + let mut dbm = DBM::in_memory().unwrap(); + let tower_id = get_random_user_id(); + let appointment = generate_random_appointment(None); + + // If there is no appointment receipt for the given (locator, tower_id) pair, Error::NotFound is returned + // Try first with both being unknown + assert!(matches!( + dbm.load_appointment_receipt(tower_id, appointment.locator), + Err(Error::NotFound) + )); + + // Add the tower but not the appointment and try again + let net_addr = "talaia.watch"; + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id, net_addr, &receipt) + .unwrap(); + + assert!(matches!( + dbm.load_appointment_receipt(tower_id, appointment.locator), + Err(Error::NotFound) + )); + + // Add both + let tower_summary = TowerSummary::new( + net_addr.into(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + let appointment_receipt = AppointmentReceipt::with_signature( + "user_signature".into(), + 42, + "tower_signature".into(), + ); + dbm.store_appointment_receipt( + tower_id, + appointment.locator, + tower_summary.available_slots, + &appointment_receipt, + ) + .unwrap(); + + assert_eq!( + dbm.load_appointment_receipt(tower_id, appointment.locator) + .unwrap(), + appointment_receipt + ); + } + #[test] fn test_load_appointment_locators() { // `load_appointment_locators` is used to load locators from either `appointment_receipts`, `pending_appointments` or `invalid_appointments` diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index c5f3a16..c64982b 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -131,6 +131,68 @@ async fn register( Ok(json!(receipt)) } +/// Gets the latest registration receipt from the client to a given tower (if it exists). +/// +/// This is pulled from the database +async fn get_registration_receipt( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; + let state = plugin.state().lock().unwrap(); + + let response = state.get_registration_receipt(tower_id).map_err(|_| { + anyhow!( + "Cannot find {} within the known towers. Have you registered?", + tower_id + ) + })?; + + Ok(json!(response)) +} + +/// Gets the subscription information directly form the tower. +async fn get_subscription_info( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; + + let user_sk = plugin.state().lock().unwrap().user_sk; + let tower_net_addr = { + let state = plugin.state().lock().unwrap(); + if let Some(info) = state.towers.get(&tower_id) { + Ok(info.net_addr.clone()) + } else { + Err(anyhow!("Unknown tower id: {}", tower_id)) + } + }?; + + let get_subscription_info = format!("{}/get_subscription_info", tower_net_addr); + let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk).unwrap(); + + let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( + post_request( + &get_subscription_info, + &common_msgs::GetSubscriptionInfoRequest { signature }, + ) + .await, + ) + .await + .map_err(|e| { + if e.is_connection() { + plugin + .state() + .lock() + .unwrap() + .set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); + } + to_cln_error(e) + })?; + + Ok(json!(response)) +} + /// Gets information about an appointment from the tower. async fn get_appointment( plugin: Plugin>>, @@ -180,44 +242,32 @@ async fn get_appointment( Ok(json!(response)) } -/// Gets the subscription information directly form the tower. -async fn get_subscription_info( +/// Gets an appointment receipt from the client given a tower_id and a locator (if it exists). +/// +/// This is pulled from the database +async fn get_appointment_receipt( plugin: Plugin>>, v: serde_json::Value, ) -> Result { - let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; + let params = GetAppointmentParams::try_from(v).map_err(|x| anyhow!(x))?; + let state = plugin.state().lock().unwrap(); - let user_sk = plugin.state().lock().unwrap().user_sk; - let tower_net_addr = { - let state = plugin.state().lock().unwrap(); - if let Some(info) = state.towers.get(&tower_id) { - Ok(info.net_addr.clone()) - } else { - Err(anyhow!("Unknown tower id: {}", tower_id)) - } - }?; - - let get_subscription_info = format!("{}/get_subscription_info", tower_net_addr); - let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk).unwrap(); - - let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( - post_request( - &get_subscription_info, - &common_msgs::GetSubscriptionInfoRequest { signature }, - ) - .await, - ) - .await - .map_err(|e| { - if e.is_connection() { - plugin - .state() - .lock() - .unwrap() - .set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); - } - to_cln_error(e) - })?; + let response = state + .get_appointment_receipt(params.tower_id, params.locator) + .map_err(|_| { + if state.towers.contains_key(¶ms.tower_id) { + anyhow!( + "Cannot find {} within {}. Did you send that appointment?", + params.locator, + params.tower_id + ) + } else { + anyhow!( + "Cannot find {} within the known towers. Have you registered?", + params.tower_id + ) + } + })?; Ok(json!(response)) } @@ -283,6 +333,21 @@ async fn retry_tower( } } +/// Forgets about a tower wiping out all local data associated to it. +async fn abandon_tower( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; + let mut state = plugin.state().lock().unwrap(); + if state.towers.get(&tower_id).is_some() { + state.remove_tower(tower_id).unwrap(); + Ok(json!(format!("{} successfully abandoned", tower_id))) + } else { + Err(anyhow!("Unknown tower {}", tower_id)) + } +} + /// Sends an appointment to all registered towers for every new commitment transaction. /// /// The appointment is built using the data provided by the backend (dispute txid and penalty transaction). @@ -444,11 +509,19 @@ async fn main() -> Result<(), Error> { "registertower", "Registers the client public key (user id) with the tower.", register, + ).rpcmethod( + "getregistrationreceipt", + "Gets the latest registration receipt given a tower id.", + get_registration_receipt, ) .rpcmethod( "getappointment", "Gets appointment data from the tower given the tower id and the locator.", get_appointment, + ).rpcmethod( + "getappointmentreceipt", + "Gets a (local) appointment receipt given a tower id and an locator.", + get_appointment_receipt, ) .rpcmethod( "getsubscriptioninfo", @@ -466,6 +539,11 @@ async fn main() -> Result<(), Error> { "Retries to send pending appointment to an unreachable tower.", retry_tower, ) + .rpcmethod( + "abandontower", + "Forgets about a tower and wipes all local data.", + abandon_tower, + ) .hook("commitment_revocation", on_commitment_revocation); if let Some(plugin) = builder.start().await? { diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 1f796fa..6decb63 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -36,10 +36,15 @@ impl Retrier { loop { let tower_id = unreachable_towers.recv().await.unwrap(); - self.wt_client - .lock() - .unwrap() - .set_tower_status(tower_id, crate::TowerStatus::TemporaryUnreachable); + { + // Not start a retry if the tower is flagged to be abandoned + let mut wt_client = self.wt_client.lock().unwrap(); + if wt_client.towers.get(&tower_id).is_none() { + log::info!("Skipping retrying abandoned tower {}", tower_id); + continue; + } + wt_client.set_tower_status(tower_id, crate::TowerStatus::TemporaryUnreachable); + } log::info!("Retrying tower {}", tower_id); match retry_notify( @@ -67,15 +72,13 @@ impl Retrier { // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior) let mut wt_client = self.wt_client.lock().unwrap(); - if wt_client - .towers - .get(&tower_id) - .unwrap() - .status - .is_unreachable() - { - log::warn!("Setting {} as unreachable", tower_id); - wt_client.set_tower_status(tower_id, crate::TowerStatus::Unreachable); + if let Some(tower) = wt_client.towers.get_mut(&tower_id) { + if tower.status.is_unreachable() { + log::warn!("Setting {} as unreachable", tower_id); + wt_client.set_tower_status(tower_id, crate::TowerStatus::Unreachable); + } + } else { + log::info!("Skipping retrying abandoned tower {}", tower_id); } } } @@ -86,6 +89,10 @@ impl Retrier { // Create a new scope so we can get all the data only locking the WTClient once. let (appointments, net_addr, user_sk) = { let wt_client = self.wt_client.lock().unwrap(); + if wt_client.towers.get(&tower_id).is_none() { + return Err(Error::permanent("Tower was abandoned. Skipping retry")); + } + let appointments = wt_client .dbm .lock() @@ -410,7 +417,7 @@ mod tests { } #[tokio::test] - async fn test_retry_misbehaving() { + async fn test_manage_retry_misbehaving() { let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); let (tx, rx) = unbounded_channel(); let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); @@ -474,6 +481,43 @@ mod tests { fs::remove_dir_all(tmp_path).await.unwrap(); } + #[tokio::test] + async fn test_manage_retry_abandoned() { + let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let (tx, rx) = unbounded_channel(); + let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let server = MockServer::start(); + + // Add a tower with pending appointments + let (_, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + let receipt = get_random_registration_receipt(); + wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, server.base_url(), &receipt) + .unwrap(); + + // Remove the tower (to simulate it has been abandoned) + wt_client.lock().unwrap().remove_tower(tower_id).unwrap(); + + // Start the task and send the tower to the channel for retry + let wt_client_clone = wt_client.clone(); + let task = tokio::spawn(async move { + Retrier::new(wt_client_clone, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry(rx) + .await + }); + + // Send the id and check how it gets removed + tx.send(tower_id).unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); + + task.abort(); + fs::remove_dir_all(tmp_path).await.unwrap(); + } + #[tokio::test] async fn test_add_appointment() { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -715,4 +759,36 @@ mod tests { fs::remove_dir_all(tmp_path).await.unwrap(); } + + #[tokio::test] + async fn test_add_appointment_abandoned() { + let (_, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.into(), unbounded_channel().0).await, + )); + let server = MockServer::start(); + + // The tower we'd like to retry sending appointments to has to exist within the plugin + let receipt = get_random_registration_receipt(); + wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, server.base_url(), &receipt) + .unwrap(); + + // Remove the tower (to simulate it has been abandoned) + wt_client.lock().unwrap().remove_tower(tower_id).unwrap(); + + // If there are no pending appointments the method will simply return + let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; + + assert_eq!( + r, + Err(Error::permanent("Tower was abandoned. Skipping retry")) + ); + + fs::remove_dir_all(tmp_path).await.unwrap(); + } } diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index e2bf439..f75ca10 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -116,6 +116,17 @@ impl WTClient { Ok(()) } + /// Gets the latest registration receipt of a given tower. + pub fn get_registration_receipt( + &self, + tower_id: TowerId, + ) -> Result { + self.dbm + .lock() + .unwrap() + .load_registration_receipt(tower_id, self.user_id) + } + /// Loads a tower record from the database. pub fn load_tower_info(&self, tower_id: TowerId) -> Result { self.dbm.lock().unwrap().load_tower_record(tower_id) @@ -159,6 +170,18 @@ impl WTClient { } } + /// Gets an appointment receipt from the database (if found). + pub fn get_appointment_receipt( + &self, + tower_id: TowerId, + locator: Locator, + ) -> Result { + self.dbm + .lock() + .unwrap() + .load_appointment_receipt(tower_id, locator) + } + /// Adds a pending appointment to the tower record. pub fn add_pending_appointment(&mut self, tower_id: TowerId, appointment: &Appointment) { if let Some(tower) = self.towers.get_mut(&tower_id) { @@ -226,6 +249,18 @@ impl WTClient { log::error!("Cannot flag tower. Unknown tower_id: {}", tower_id); } } + + /// Removes a tower from the client (both memory and database). + /// + /// Any data associated to the tower will be deleted (i.e. links to appointments) + pub fn remove_tower(&mut self, tower_id: TowerId) -> Result<(), DBError> { + if self.towers.contains_key(&tower_id) { + self.towers.remove(&tower_id); + self.dbm.lock().unwrap().remove_tower_record(tower_id) + } else { + Err(DBError::NotFound) + } + } } #[cfg(test)] @@ -705,4 +740,166 @@ mod tests { assert_eq!(loaded_info.misbehaving_proof, Some(proof)); assert!(loaded_info.appointments.contains_key(&appointment.locator)); } + + #[tokio::test] + async fn test_remove_tower() { + let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + + let receipt = get_random_registration_receipt(); + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + let tower_info = TowerInfo::empty( + "talaia.watch".into(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + + // Add the tower and check it is there + wt_client + .add_update_tower(tower_id, tower_info.net_addr.clone(), &receipt) + .unwrap(); + assert_eq!( + wt_client.towers.get(&tower_id), + Some(&TowerSummary::from(tower_info.clone())) + ); + assert_eq!(wt_client.load_tower_info(tower_id).unwrap(), tower_info); + + // Remove the tower and check it is not there anymore + wt_client.remove_tower(tower_id).unwrap(); + assert!(matches!( + wt_client.load_tower_info(tower_id), + Err(DBError::NotFound) + )); + assert!(!wt_client.towers.contains_key(&tower_id)); + + // Try again but this time with an associated appointment to check that it also gets removed + wt_client + .add_update_tower(tower_id, tower_info.net_addr, &receipt) + .unwrap(); + + let locator = generate_random_appointment(None).locator; + let registration_receipt = get_random_registration_receipt(); + let appointment_receipt = get_random_appointment_receipt(tower_sk); + + // If we call this on an unknown tower it will simply do nothing + wt_client.add_appointment_receipt( + tower_id, + locator, + registration_receipt.available_slots(), + &appointment_receipt, + ); + assert!(wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower_id)); + + // Remove and check both the tower and the appointment + wt_client.remove_tower(tower_id).unwrap(); + assert!(matches!( + wt_client.load_tower_info(tower_id), + Err(DBError::NotFound) + )); + assert!(!wt_client.towers.contains_key(&tower_id)); + assert!(!wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower_id)); + + fs::remove_dir_all(tmp_path).await.unwrap(); + } + + #[tokio::test] + async fn test_remove_tower_shared_appointment() { + // Lets test removing a tower that has associated data shared with another tower. + // For instance, having an appointment that was sent to two towers, and then deleting one of them + // should only remove the link between the tower and the appointment, but not delete the data. + let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + + let receipt = get_random_registration_receipt(); + let (tower1_sk, tower1_pk) = cryptography::get_random_keypair(); + let tower1_id = TowerId(tower1_pk); + let (tower2_sk, tower2_pk) = cryptography::get_random_keypair(); + let tower2_id = TowerId(tower2_pk); + + let tower_info = TowerInfo::empty( + "talaia.watch".into(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ); + wt_client + .add_update_tower(tower1_id, tower_info.net_addr.clone(), &receipt) + .unwrap(); + wt_client + .add_update_tower(tower2_id, tower_info.net_addr, &receipt) + .unwrap(); + + let locator = generate_random_appointment(None).locator; + let registration_receipt = get_random_registration_receipt(); + let appointment_receipt_1 = get_random_appointment_receipt(tower1_sk); + let appointment_receipt_2 = get_random_appointment_receipt(tower2_sk); + + wt_client.add_appointment_receipt( + tower1_id, + locator, + registration_receipt.available_slots(), + &appointment_receipt_1, + ); + wt_client.add_appointment_receipt( + tower2_id, + locator, + registration_receipt.available_slots(), + &appointment_receipt_2, + ); + + // Check that the data exists in both towers + assert!(wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower2_id)); + + // Remove tower1 and check that the appointment receipt can still be found for tower2 + wt_client.remove_tower(tower1_id).unwrap(); + assert!(matches!( + wt_client.load_tower_info(tower1_id), + Err(DBError::NotFound) + )); + + assert!(!wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client + .dbm + .lock() + .unwrap() + .appointment_receipt_exists(locator, tower2_id)); + + fs::remove_dir_all(tmp_path).await.unwrap(); + } + + #[tokio::test] + async fn test_remove_inexistent_tower() { + let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + + assert!(matches!( + wt_client.remove_tower(get_random_user_id()), + Err(DBError::NotFound) + )); + + fs::remove_dir_all(tmp_path).await.unwrap(); + } } From a42a5eebadc1690b06903b925e08d35ac0c36007 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 11 Aug 2022 15:51:21 +0200 Subject: [PATCH 008/119] Fixes missing tmp_dir for watchtower-plugin Some of the tmp paths in the `watchtower-plugin` tests were not using `TempDir` and still manually removing the directories. --- watchtower-plugin/src/retrier.rs | 81 +++++++++++++----------------- watchtower-plugin/src/wt_client.rs | 27 ++++------ 2 files changed, 45 insertions(+), 63 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 6decb63..9235753 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -37,7 +37,7 @@ impl Retrier { loop { let tower_id = unreachable_towers.recv().await.unwrap(); { - // Not start a retry if the tower is flagged to be abandoned + // Not start a retry if the tower has been abandoned let mut wt_client = self.wt_client.lock().unwrap(); if wt_client.towers.get(&tower_id).is_none() { log::info!("Skipping retrying abandoned tower {}", tower_id); @@ -175,7 +175,7 @@ mod tests { use httpmock::prelude::*; use serde_json::json; - use tokio::fs; + use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; @@ -201,9 +201,11 @@ mod tests { // TODO: It'll be nice to toggle the mock on and off instead of having it always on. Not sure MockServer allows that though: // https://github.com/alexliesenfeld/httpmock/issues/67 async fn test_manage_retry_reachable() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); let server = MockServer::start(); // Add a tower with pending appointments @@ -271,14 +273,15 @@ mod tests { api_mock.assert(); task.abort(); - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_manage_retry_unreachable() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -335,14 +338,15 @@ mod tests { ); task.abort(); - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_manage_retry_rejected() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); let server = MockServer::start(); // Add a tower with pending appointments @@ -413,14 +417,15 @@ mod tests { api_mock.assert(); task.abort(); - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_manage_retry_misbehaving() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); let server = MockServer::start(); // Add a tower with pending appointments @@ -478,14 +483,15 @@ mod tests { api_mock.assert(); task.abort(); - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_manage_retry_abandoned() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(tmp_path.into(), tx.clone()).await)); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); let server = MockServer::start(); // Add a tower with pending appointments @@ -515,16 +521,15 @@ mod tests { assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); task.abort(); - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment() { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -561,17 +566,15 @@ mod tests { let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; assert_eq!(r, Ok(())); api_mock.assert(); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_no_pending() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -587,17 +590,15 @@ mod tests { let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; assert_eq!(r, Ok(())); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_misbehaving() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -633,17 +634,15 @@ mod tests { let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; assert_eq!(r, Err(Error::permanent("Tower misbehaved"))); api_mock.assert(); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_unreachable() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); // The tower we'd like to retry sending appointments to has to exist within the plugin @@ -662,17 +661,15 @@ mod tests { .add_pending_appointment(tower_id, &appointment); let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; assert_eq!(r, Err(Error::transient("Tower cannot be reached"))); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_subscription_error() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -704,17 +701,15 @@ mod tests { assert_eq!(r, Err(Error::transient("Subscription error"))); api_mock.assert(); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_rejected() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -756,17 +751,15 @@ mod tests { .unwrap() .invalid_appointments .contains(&appointment.locator)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_add_appointment_abandoned() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let wt_client = Arc::new(Mutex::new( - WTClient::new(tmp_path.into(), unbounded_channel().0).await, + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); let server = MockServer::start(); @@ -788,7 +781,5 @@ mod tests { r, Err(Error::permanent("Tower was abandoned. Skipping retry")) ); - - fs::remove_dir_all(tmp_path).await.unwrap(); } } diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index f75ca10..788cef7 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -514,8 +514,6 @@ mod tests { .lock() .unwrap() .appointment_exists(appointment.locator)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] @@ -608,8 +606,6 @@ mod tests { .lock() .unwrap() .appointment_exists(appointment.locator)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] @@ -701,8 +697,6 @@ mod tests { .lock() .unwrap() .appointment_exists(appointment.locator)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] @@ -743,8 +737,9 @@ mod tests { #[tokio::test] async fn test_remove_tower() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); - let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let mut wt_client = + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let receipt = get_random_registration_receipt(); let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -808,8 +803,6 @@ mod tests { .lock() .unwrap() .appointment_receipt_exists(locator, tower_id)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] @@ -817,8 +810,9 @@ mod tests { // Lets test removing a tower that has associated data shared with another tower. // For instance, having an appointment that was sent to two towers, and then deleting one of them // should only remove the link between the tower and the appointment, but not delete the data. - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); - let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let mut wt_client = + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let receipt = get_random_registration_receipt(); let (tower1_sk, tower1_pk) = cryptography::get_random_keypair(); @@ -886,20 +880,17 @@ mod tests { .lock() .unwrap() .appointment_receipt_exists(locator, tower2_id)); - - fs::remove_dir_all(tmp_path).await.unwrap(); } #[tokio::test] async fn test_remove_inexistent_tower() { - let tmp_path = &format!(".watchtower_{}/", get_random_user_id()); - let mut wt_client = WTClient::new(tmp_path.into(), unbounded_channel().0).await; + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let mut wt_client = + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; assert!(matches!( wt_client.remove_tower(get_random_user_id()), Err(DBError::NotFound) )); - - fs::remove_dir_all(tmp_path).await.unwrap(); } } From 237368140244acbcee1cc5c1cbc9785d24dfddd2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 17 Aug 2022 10:19:18 +0200 Subject: [PATCH 009/119] Persists Tor secret key to disk --- teos/src/api/tor.rs | 35 ++++++++++++++++++++++++++++++++++- teos/src/main.rs | 12 +++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/teos/src/api/tor.rs b/teos/src/api/tor.rs index 9d821d3..18992eb 100644 --- a/teos/src/api/tor.rs +++ b/teos/src/api/tor.rs @@ -1,16 +1,42 @@ +use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::net::SocketAddr; +use std::path::PathBuf; +use tokio::fs; use tokio::net::TcpStream; use tokio::time::{sleep, Duration}; use torut::control::UnauthenticatedConn; use torut::onion::TorSecretKeyV3; use triggered::Listener; +/// Loads a Tor key from disk (if found). +async fn load_tor_key(path: &PathBuf) -> Option { + log::info!("Loading Tor secret key from disk"); + let key = fs::read(path.join("onion_v3_sk")) + .await + .map_err(|e| log::error!("Cannot load Tor secret key. {}", e)) + .ok()?; + let key: [u8; 64] = key + .try_into() + .map_err(|_| log::error!("Cannot convert loaded data into Tor secret key")) + .ok()?; + + Some(TorSecretKeyV3::from(key)) +} + +/// Stores a Tor key to disk. +async fn store_tor_key(key: &TorSecretKeyV3, path: &PathBuf) { + if let Err(e) = fs::write(path.join("onion_v3_sk"), key.as_bytes()).await { + log::error!("Cannot store Tor secret key. {}", e); + } +} + /// Expose an onion service that re-directs to the public api. pub async fn expose_onion_service( tor_control_port: u16, api_port: u16, onion_port: u16, + path: PathBuf, shutdown_signal_tor: Listener, ) -> Result<(), Error> { let stream = connect_tor_cp(format!("127.0.0.1:{}", tor_control_port).parse().unwrap()) @@ -39,7 +65,14 @@ pub async fn expose_onion_service( auth_conn.set_async_event_handler(Some(|_| async move { Ok(()) })); - let key = TorSecretKeyV3::generate(); + let key = if let Some(key) = load_tor_key(&path).await { + key + } else { + log::info!("Generating fresh Tor secret key"); + let key = TorSecretKeyV3::generate(); + store_tor_key(&key, &path).await; + key + }; auth_conn .add_onion_v3( diff --git a/teos/src/main.rs b/teos/src/main.rs index 4602186..fea0197 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -298,9 +298,15 @@ async fn main() { let onion_port = conf.onion_hidden_service_port; tor_task = Some(task::spawn(async move { - tor::expose_onion_service(tor_control_port, api_port, onion_port, shutdown_signal_tor) - .await - .unwrap(); + tor::expose_onion_service( + tor_control_port, + api_port, + onion_port, + path_network, + shutdown_signal_tor, + ) + .await + .unwrap(); })); } From 3b7842a0f42d29f0b841288514669528b250b84e Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 17 Aug 2022 14:16:50 +0200 Subject: [PATCH 010/119] Properly capitalizes Tor See https://support.torproject.org/about/why-is-it-called-tor/ --- README.md | 8 +++---- teos/Cargo.toml | 3 ++- teos/src/api/tor.rs | 54 +++++++++++++++++++++++++++++++++++++-------- teos/src/config.rs | 4 ++-- teos/src/main.rs | 6 ++++- 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7a6ad48..87be677 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,11 @@ btc_network = regtest ### Running `teosd` with tor -This requires a tor daemon running on the same machine as `teosd` and a control port open on that daemon. +This requires a Tor daemon running on the same machine as `teosd` and a control port open on that daemon. -Download tor from the [torproject site](https://www.torproject.org/download/). +Download Tor from the [torproject site](https://www.torproject.org/download/). -To open tor's control port, you add the following to the tor config file ([source](https://2019.www.torproject.org/docs/faq.html.en#torrc)): +To open tor's control port, you add the following to the Tor config file ([source](https://2019.www.torproject.org/docs/faq.html.en#torrc)): ``` ## The port on which Tor will listen for local connections from Tor @@ -85,7 +85,7 @@ CookieAuthentication 1 CookieAuthFileGroupReadable 1 ``` -Once the tor daemon is running, and the control port is open, make sure to enable the `tor_support` flag `teosd`. +Once the Tor daemon is running, and the control port is open, make sure to enable the `tor_support` flag `teosd`. ### Tower id and signing key diff --git a/teos/Cargo.toml b/teos/Cargo.toml index c03291a..30dafea 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -48,6 +48,7 @@ tonic-build = "0.6" [dev-dependencies] chunked_transfer = "1.4" -rand = "0.8.4" jsonrpc-http-server = "17.1.0" +rand = "0.8.4" +tempdir = "0.3.7" tokio-stream = { version = "0.1.5", features = [ "net" ] } diff --git a/teos/src/api/tor.rs b/teos/src/api/tor.rs index 18992eb..194a513 100644 --- a/teos/src/api/tor.rs +++ b/teos/src/api/tor.rs @@ -2,19 +2,20 @@ use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::net::SocketAddr; use std::path::PathBuf; + use tokio::fs; use tokio::net::TcpStream; use tokio::time::{sleep, Duration}; use torut::control::UnauthenticatedConn; use torut::onion::TorSecretKeyV3; -use triggered::Listener; +use triggered::{Listener, Trigger}; /// Loads a Tor key from disk (if found). -async fn load_tor_key(path: &PathBuf) -> Option { +async fn load_tor_key(path: PathBuf) -> Option { log::info!("Loading Tor secret key from disk"); let key = fs::read(path.join("onion_v3_sk")) .await - .map_err(|e| log::error!("Cannot load Tor secret key. {}", e)) + .map_err(|e| log::warn!("Tor secret key cannot be loaded. {}", e)) .ok()?; let key: [u8; 64] = key .try_into() @@ -25,7 +26,7 @@ async fn load_tor_key(path: &PathBuf) -> Option { } /// Stores a Tor key to disk. -async fn store_tor_key(key: &TorSecretKeyV3, path: &PathBuf) { +async fn store_tor_key(key: &TorSecretKeyV3, path: PathBuf) { if let Err(e) = fs::write(path.join("onion_v3_sk"), key.as_bytes()).await { log::error!("Cannot store Tor secret key. {}", e); } @@ -37,6 +38,7 @@ pub async fn expose_onion_service( api_port: u16, onion_port: u16, path: PathBuf, + service_ready: Trigger, shutdown_signal_tor: Listener, ) -> Result<(), Error> { let stream = connect_tor_cp(format!("127.0.0.1:{}", tor_control_port).parse().unwrap()) @@ -65,12 +67,12 @@ pub async fn expose_onion_service( auth_conn.set_async_event_handler(Some(|_| async move { Ok(()) })); - let key = if let Some(key) = load_tor_key(&path).await { + let key = if let Some(key) = load_tor_key(path.clone()).await { key } else { log::info!("Generating fresh Tor secret key"); let key = TorSecretKeyV3::generate(); - store_tor_key(&key, &path).await; + store_tor_key(&key, path).await; key }; @@ -96,6 +98,7 @@ pub async fn expose_onion_service( })?; print_onion_service(key.clone(), onion_port); + service_ready.trigger(); // NOTE: Needed to keep connection with control port & hidden service running, as soon as we leave // this function the control port stream is dropped and the hidden service is killed @@ -121,7 +124,7 @@ async fn connect_tor_cp(addr: SocketAddr) -> Result { let sock = TcpStream::connect(addr).await.map_err(|_| { Error::new( ErrorKind::ConnectionRefused, - "failed to connect to tor control port", + "failed to connect to Tor control port", ) })?; Ok(sock) @@ -130,12 +133,45 @@ async fn connect_tor_cp(addr: SocketAddr) -> Result { fn print_onion_service(key: TorSecretKeyV3, onion_port: u16) { let onion_addr = key.public().get_onion_address(); let onion = format!("{}:{}", onion_addr, onion_port); - log::info!("onion service: {}", onion); + log::info!("Onion service: {}", onion); } #[cfg(test)] mod tests { use super::*; + use tempdir::TempDir; + + use teos_common::test_utils::get_random_user_id; + + #[tokio::test] + async fn test_store_load_key() { + let key = TorSecretKeyV3::generate(); + let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); + + store_tor_key(&key, tmp_path.path().into()).await; + let loaded_key = load_tor_key(tmp_path.path().into()).await; + + assert_eq!(key, loaded_key.unwrap()) + } + + #[tokio::test] + async fn test_load_key_inexistent() { + let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); + let loaded_key = load_tor_key(tmp_path.path().into()).await; + + assert_eq!(loaded_key, None); + } + + #[tokio::test] + async fn test_load_key_wrong_format() { + let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); + fs::write(tmp_path.path().join("onion_v3_sk"), "random stuff") + .await + .unwrap(); + let loaded_key = load_tor_key(tmp_path.path().into()).await; + + assert_eq!(loaded_key, None); + } #[tokio::test] async fn test_connect_tor_cp_fail() { @@ -144,7 +180,7 @@ mod tests { match connect_tor_cp(addr).await { Ok(_) => {} Err(e) => { - assert_eq!("failed to connect to tor control port", e.to_string()) + assert_eq!("failed to connect to Tor control port", e.to_string()) } } } diff --git a/teos/src/config.rs b/teos/src/config.rs index ceee19d..722c26b 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -97,11 +97,11 @@ pub struct Opt { #[structopt(long)] pub overwrite_key: bool, - /// If set, creates a tor endpoint to serve API data. This endpoint is additional to the clearnet HTTP API + /// If set, creates a Tor endpoint to serve API data. This endpoint is additional to the clearnet HTTP API #[structopt(long)] pub tor_support: bool, - /// tor control port [default: 9051] + /// Tor control port [default: 9051] #[structopt(long)] pub tor_control_port: Option, diff --git a/teos/src/main.rs b/teos/src/main.rs index fea0197..0778cf6 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -291,8 +291,9 @@ async fn main() { // Add Tor Onion Service for public API let mut tor_task = Option::None; + let (tor_service_ready, ready_signal_tor) = triggered::trigger(); if conf.tor_support { - log::info!("Starting up hidden tor service"); + log::info!("Starting up Tor hidden service"); let tor_control_port = conf.tor_control_port; let api_port = conf.api_port; let onion_port = conf.onion_hidden_service_port; @@ -303,11 +304,14 @@ async fn main() { api_port, onion_port, path_network, + tor_service_ready, shutdown_signal_tor, ) .await .unwrap(); })); + + ready_signal_tor.await } log::info!("Tower ready"); From e70561d39eee5257273bdd272b24257611c592af Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 24 Aug 2022 13:24:19 +0200 Subject: [PATCH 011/119] Updates register for re-register Makes it so if a user registers more than once the expiry is not based on the current height but on the old expiry. --- teos/src/gatekeeper.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 8441e03..3af952a 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -175,7 +175,10 @@ impl Gatekeeper { .available_slots .checked_add(self.subscription_slots) .ok_or(MaxSlotsReached)?; - user_info.subscription_expiry = block_count + self.subscription_duration; + user_info.subscription_expiry = user_info + .subscription_expiry + .checked_add(self.subscription_duration) + .unwrap_or(u32::MAX); self.dbm.lock().unwrap().update_user(user_id, user_info); user_info @@ -499,13 +502,10 @@ mod tests { .store(chain.get_block_count(), Ordering::Relaxed); let updated_receipt = gatekeeper.add_update_user(user_id).unwrap(); - assert_eq!( - updated_receipt.available_slots(), - receipt.available_slots() * 2 - ); + assert_eq!(updated_receipt.available_slots(), SLOTS * 2); assert_eq!( updated_receipt.subscription_expiry(), - receipt.subscription_expiry() + 1 + START_HEIGHT as u32 + DURATION * 2 ); // Data in the database should have been updated too From 47ba8b0cb988b8ae18c840adb64f7b24a6805ed4 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 24 Aug 2022 14:08:05 +0200 Subject: [PATCH 012/119] Makes Tor task panics more user friendly --- teos/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/teos/src/main.rs b/teos/src/main.rs index 0778cf6..789f197 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -299,7 +299,7 @@ async fn main() { let onion_port = conf.onion_hidden_service_port; tor_task = Some(task::spawn(async move { - tor::expose_onion_service( + if let Err(e) = tor::expose_onion_service( tor_control_port, api_port, onion_port, @@ -308,7 +308,10 @@ async fn main() { shutdown_signal_tor, ) .await - .unwrap(); + { + eprintln!("Cannot connect to the Tor backend: {}", e); + std::process::exit(1); + } })); ready_signal_tor.await From eac238628e77abda81c2f612c25d207c4e2e2807 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 18 Aug 2022 13:18:40 +0200 Subject: [PATCH 013/119] Adds Tor support to cln-plugin --- watchtower-plugin/Cargo.toml | 2 +- watchtower-plugin/src/net/http.rs | 34 +++++++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index fcf85f8..df19470 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" backoff = { version = "0.4.0", features = ["tokio"] } hex = { version = "0.4.3", features = [ "serde" ] } home = "0.5.3" -reqwest = { version = "0.11", features = [ "blocking", "json" ] } +reqwest = { version = "0.11", features = [ "blocking", "json", "socks" ] } log = "0.4.16" rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index d00bb40..d6ad7f6 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -119,21 +119,25 @@ pub async fn send_appointment( /// Generic function to post different types of requests to the tower. pub async fn post_request(endpoint: &str, data: S) -> Result { - reqwest::Client::new() - .post(endpoint) - .json(&data) - .send() - .await - .map_err(|e| { - log::error!("{}", e); - if e.is_connect() | e.is_timeout() { - RequestError::ConnectionError( - "Cannot connect to the tower. Connection refused".into(), - ) - } else { - RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".into()) - } - }) + let client = if endpoint.contains(".onion:") { + let proxy = reqwest::Proxy::http("socks5h://127.0.0.1:9050") + .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?; + reqwest::Client::builder() + .proxy(proxy) + .build() + .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? + } else { + reqwest::Client::new() + }; + + client.post(endpoint).json(&data).send().await.map_err(|e| { + log::error!("{:?}", e); + if e.is_connect() | e.is_timeout() { + RequestError::ConnectionError("Cannot connect to the tower. Connection refused".into()) + } else { + RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".into()) + } + }) } /// Generic function to process the response of a given post request. From 0708b15157abc3970b30fd7b336f9189476e0e95 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 23 Aug 2022 13:50:15 +0200 Subject: [PATCH 014/119] Adds proxy option --- watchtower-plugin/src/main.rs | 36 +++++++++++++++++----- watchtower-plugin/src/net/http.rs | 49 ++++++++++++++++++++++-------- watchtower-plugin/src/retrier.rs | 5 +-- watchtower-plugin/src/wt_client.rs | 3 ++ 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index c64982b..036350e 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -70,6 +70,8 @@ async fn register( tower_net_addr = format!("http://{}", tower_net_addr) } + let proxy = plugin.state().lock().unwrap().proxy.clone(); + let register_endpoint = format!("{}/register", tower_net_addr); log::info!("Registering in the Eye of Satoshi (tower_id={})", tower_id); @@ -79,6 +81,7 @@ async fn register( &common_msgs::RegisterRequest { user_id: user_id.to_vec(), }, + proxy, ) .await, ) @@ -158,11 +161,10 @@ async fn get_subscription_info( ) -> Result { let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; - let user_sk = plugin.state().lock().unwrap().user_sk; - let tower_net_addr = { + let (user_sk, tower_net_addr, proxy) = { let state = plugin.state().lock().unwrap(); if let Some(info) = state.towers.get(&tower_id) { - Ok(info.net_addr.clone()) + Ok((state.user_sk, info.net_addr.clone(), state.proxy.clone())) } else { Err(anyhow!("Unknown tower id: {}", tower_id)) } @@ -175,6 +177,7 @@ async fn get_subscription_info( post_request( &get_subscription_info, &common_msgs::GetSubscriptionInfoRequest { signature }, + proxy, ) .await, ) @@ -200,11 +203,10 @@ async fn get_appointment( ) -> Result { let params = GetAppointmentParams::try_from(v).map_err(|x| anyhow!(x))?; - let user_sk = plugin.state().lock().unwrap().user_sk; - let tower_net_addr = { + let (user_sk, tower_net_addr, proxy) = { let state = plugin.state().lock().unwrap(); if let Some(info) = state.towers.get(¶ms.tower_id) { - Ok(info.net_addr.clone()) + Ok((state.user_sk, info.net_addr.clone(), state.proxy.clone())) } else { Err(anyhow!("Unknown tower id: {}", params.tower_id)) } @@ -224,6 +226,7 @@ async fn get_appointment( locator: params.locator.to_vec(), signature, }, + proxy, ) .await, ) @@ -391,9 +394,13 @@ async fn on_commitment_revocation( .map(|(id, info)| (*id, info.net_addr.clone(), info.status)) .collect::>(); + let proxy = plugin.state().lock().unwrap().proxy.clone(); + for (tower_id, net_addr, status) in towers { if status.is_reachable() { - match add_appointment(tower_id, &net_addr, &appointment, &signature).await { + match add_appointment(tower_id, &net_addr, proxy.clone(), &appointment, &signature) + .await + { Ok((slots, receipt)) => { plugin .state() @@ -500,6 +507,11 @@ async fn main() -> Result<(), Error> { Value::Integer(900), "the time (in seconds) after where the retrier will give up trying to send data to a temporary unreachable tower", )) + .option(ConfigOption::new( + "watchtower-proxy", + Value::String(String::new()), + "Socks v5 proxy IP address and port for the watchtower client", + )) .option(ConfigOption::new( "dev-watchtower-max-retry-interval", Value::Integer(60), @@ -549,6 +561,16 @@ async fn main() -> Result<(), Error> { if let Some(plugin) = builder.start().await? { // FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap // given that we are certain the option exists. + state_clone.lock().unwrap().proxy = + if let Value::String(x) = plugin.option("watchtower-proxy").unwrap() { + if !x.is_empty() { + Some(x) + } else { + None + } + } else { + None + }; let max_elapsed_time = if let Value::Integer(x) = plugin.option("watchtower-max-retry-time").unwrap() { x as u16 diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index d6ad7f6..73d71d4 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -56,6 +56,7 @@ impl From for AddAppointmentError { pub async fn add_appointment( tower_id: TowerId, tower_net_addr: &str, + proxy: Option, appointment: &Appointment, signature: &str, ) -> Result<(u32, AppointmentReceipt), AddAppointmentError> { @@ -65,7 +66,7 @@ pub async fn add_appointment( tower_id ); let (response, receipt) = - send_appointment(tower_id, tower_net_addr, appointment, signature).await?; + send_appointment(tower_id, tower_net_addr, proxy, appointment, signature).await?; log::debug!("Appointment accepted and signed by {}", tower_id); log::debug!("Remaining slots: {}", response.available_slots); log::debug!("Start block: {}", response.start_block); @@ -77,6 +78,7 @@ pub async fn add_appointment( pub async fn send_appointment( tower_id: TowerId, tower_net_addr: &str, + proxy: Option, appointment: &Appointment, signature: &str, ) -> Result<(common_msgs::AddAppointmentResponse, AppointmentReceipt), AddAppointmentError> { @@ -89,6 +91,7 @@ pub async fn send_appointment( post_request( &format!("{}/add_appointment", tower_net_addr), &request_data, + proxy, ) .await, ) @@ -118,14 +121,27 @@ pub async fn send_appointment( } /// Generic function to post different types of requests to the tower. -pub async fn post_request(endpoint: &str, data: S) -> Result { - let client = if endpoint.contains(".onion:") { - let proxy = reqwest::Proxy::http("socks5h://127.0.0.1:9050") - .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?; - reqwest::Client::builder() - .proxy(proxy) - .build() - .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? +pub async fn post_request( + endpoint: &str, + data: S, + proxy: Option, +) -> Result { + let url = reqwest::Url::parse(endpoint).map_err(|e| { + RequestError::ConnectionError(format!("Cannot connect to the given URL. {}", e)) + })?; + let client = if url.host_str().unwrap().ends_with(".onion") { + if let Some(proxy) = proxy { + let proxy = reqwest::Proxy::http(format!("socks5h://{}", proxy)) + .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?; + reqwest::Client::builder() + .proxy(proxy) + .build() + .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? + } else { + return Err(RequestError::ConnectionError( + "Cannot connect to an onion address without a proxy".into(), + )); + } } else { reqwest::Client::new() }; @@ -206,6 +222,7 @@ mod tests { let (response, receipt) = add_appointment( TowerId(tower_pk), &format!("http://{}", server.address()), + None, &appointment, appointment_receipt.user_signature(), ) @@ -237,6 +254,7 @@ mod tests { let (response, receipt) = send_appointment( TowerId(tower_pk), &format!("http://{}", server.address()), + None, &appointment, appointment_receipt.user_signature(), ) @@ -269,6 +287,7 @@ mod tests { let error = send_appointment( tower_id, &format!("http://{}", server.address()), + None, &appointment, appointment_receipt.user_signature(), ) @@ -295,6 +314,7 @@ mod tests { let error = send_appointment( get_random_user_id(), "http://server_addr", + None, &generate_random_appointment(None), "user_sig", ) @@ -321,6 +341,7 @@ mod tests { let error = send_appointment( get_random_user_id(), &format!("http://{}", server.address()), + None, &generate_random_appointment(None), "user_sig", ) @@ -353,6 +374,7 @@ mod tests { let error = send_appointment( get_random_user_id(), &format!("http://{}", server.address()), + None, &generate_random_appointment(None), "user_sig", ) @@ -379,6 +401,7 @@ mod tests { let error = send_appointment( get_random_user_id(), wrong_tower_net_addr, + None, &generate_random_appointment(None), "user_sig", ) @@ -400,7 +423,7 @@ mod tests { then.status(200).header("content-type", "application/json"); }); - let response = post_request(&format!("http://{}", server.address()), json!("")) + let response = post_request(&format!("http://{}", server.address()), json!(""), None) .await .unwrap(); @@ -413,7 +436,7 @@ mod tests { let unreachable_server_url = "http://server_addr"; assert!(matches!( - post_request(unreachable_server_url, json!("")) + post_request(unreachable_server_url, json!(""), None,) .await .unwrap_err(), RequestError::ConnectionError { .. } @@ -425,7 +448,7 @@ mod tests { let malformed_server_url = "server_addr"; assert!(matches!( - post_request(malformed_server_url, json!("")) + post_request(malformed_server_url, json!(""), None,) .await .unwrap_err(), RequestError::Unexpected { .. } @@ -444,7 +467,7 @@ mod tests { // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( - post_request(&format!("http://{}", server.address()), json!("")).await, + post_request(&format!("http://{}", server.address()), json!(""), None).await, ) .await .unwrap_err(); diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 9235753..14b092a 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -87,7 +87,7 @@ impl Retrier { async fn add_appointment(&self, tower_id: TowerId) -> Result<(), Error<&'static str>> { // Create a new scope so we can get all the data only locking the WTClient once. - let (appointments, net_addr, user_sk) = { + let (appointments, net_addr, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); if wt_client.towers.get(&tower_id).is_none() { return Err(Error::permanent("Tower was abandoned. Skipping retry")); @@ -100,13 +100,14 @@ impl Retrier { .load_appointments(tower_id, AppointmentStatus::Pending); let net_addr = wt_client.towers.get(&tower_id).unwrap().net_addr.clone(); let user_sk = wt_client.user_sk; - (appointments, net_addr, user_sk) + (appointments, net_addr, user_sk, wt_client.proxy.clone()) }; for appointment in appointments { match add_appointment( tower_id, &net_addr, + proxy.clone(), &appointment, &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), ) diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 788cef7..7c9aeb3 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -28,6 +28,8 @@ pub struct WTClient { pub user_sk: SecretKey, /// The user identifier. pub user_id: UserId, + /// Optional proxy + pub proxy: Option, } impl WTClient { @@ -70,6 +72,7 @@ impl WTClient { dbm: Arc::new(Mutex::new(dbm)), user_sk, user_id, + proxy: None, } } From c2bc0d610646442b228005efc425a8a483207e27 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 24 Aug 2022 12:56:42 +0200 Subject: [PATCH 015/119] Remove unexpected_error tests --- watchtower-plugin/src/net/http.rs | 42 ------------------------------- 1 file changed, 42 deletions(-) diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 73d71d4..a57a2a4 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -385,36 +385,6 @@ mod tests { assert!(matches!(error, AddAppointmentError::ApiError { .. })); } - #[tokio::test] - async fn test_send_appointment_unexpected() { - // An example to trigger an unexpected error would be to try to send data to a wrongly formatted url. - // This can not happen in the codebase, since the url is tested on registration, but it can be used to - // test that error path. Generally speaking, that error path should be unreachable. - let wrong_tower_net_addr = "server_addr"; - - let server = MockServer::start(); - server.mock(|when, then| { - when.method(POST).path("/add_appointment"); - then.status(200).header("content-type", "application/json"); - }); - - let error = send_appointment( - get_random_user_id(), - wrong_tower_net_addr, - None, - &generate_random_appointment(None), - "user_sig", - ) - .await - .unwrap_err(); - - if let AddAppointmentError::RequestError(e) = error { - assert!(matches!(e, RequestError::Unexpected { .. })) - } else { - panic!("Funny enough, Unexpected error was expected") - } - } - #[tokio::test] async fn test_post_request() { let server = MockServer::start(); @@ -443,18 +413,6 @@ mod tests { )); } - #[tokio::test] - async fn test_post_request_unexpected_error() { - let malformed_server_url = "server_addr"; - - assert!(matches!( - post_request(malformed_server_url, json!(""), None,) - .await - .unwrap_err(), - RequestError::Unexpected { .. } - )); - } - #[tokio::test] async fn test_process_post_response_json_error() { // `process_post_response` is a pass-trough function that maps json deserialization errors from `post_request`. From 4ce991fae076c844f1fd0108c8931ddbcb24f8dc Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 29 Aug 2022 15:45:46 +0200 Subject: [PATCH 016/119] Updates the plugin README with Tor info --- watchtower-plugin/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index e3ecdac..82f8d3f 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -109,6 +109,9 @@ Config options can be setup directly in the [CLN config file](https://github.com - `watchtower-port`: default tower API port. - `watchtower-max-retry-time`: the maximum time a retry strategy will try to reach a temporary unreachable tower before giving up. +- `watchtower-proxy`: informs the plugin that you have a SOCKS5 proxy at the given `ip:port`. Notice this is necessary if you want to connect to a tower through Tor! + +**DISCLAIMER**: This option will be eventually replaced by the CoreLN `proxy` / `always-use-proxy` options. In the current state of the `cln-plugin` crate there is no option to access the CoreLN main configuration, therefore the need for a temporary, plugin-specific, option. # Getting started From a1aea5dcec0e5e88905801ef30ddf3fd53ce949b Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 29 Aug 2022 16:02:12 +0200 Subject: [PATCH 017/119] Avoid having to hit the db if there is no data to delete --- teos/src/gatekeeper.rs | 12 +++++++----- teos/src/responder.rs | 12 +++++++----- teos/src/watcher.rs | 12 +++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 3af952a..7ae8a23 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -325,11 +325,13 @@ impl chain::Listen for Gatekeeper { // Expired user deletion is delayed. Users are deleted when their subscription is outdated, not expired. let outdated_users = self.get_outdated_user_ids(height); - self.registered_users - .lock() - .unwrap() - .retain(|id, _| !outdated_users.contains(id)); - self.dbm.lock().unwrap().batch_remove_users(&outdated_users); + if !outdated_users.is_empty() { + self.registered_users + .lock() + .unwrap() + .retain(|id, _| !outdated_users.contains(id)); + self.dbm.lock().unwrap().batch_remove_users(&outdated_users); + } // Update last known block height self.last_known_block_height diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 0e81b74..0f61394 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -472,11 +472,13 @@ impl Responder { updated_users: &HashMap, reason: DeletionReason, ) { - self.delete_trackers_from_memory(uuids, reason); - self.dbm - .lock() - .unwrap() - .batch_remove_appointments(uuids, updated_users); + if !uuids.is_empty() { + self.delete_trackers_from_memory(uuids, reason); + self.dbm + .lock() + .unwrap() + .batch_remove_appointments(uuids, updated_users); + } } } diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 750289b..5b13564 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -678,11 +678,13 @@ impl Watcher { updated_users: &HashMap, reason: DeletionReason, ) { - self.delete_appointments_from_memory(uuids, reason); - self.dbm - .lock() - .unwrap() - .batch_remove_appointments(uuids, updated_users); + if !uuids.is_empty() { + self.delete_appointments_from_memory(uuids, reason); + self.dbm + .lock() + .unwrap() + .batch_remove_appointments(uuids, updated_users); + } } /// Ges the number of users currently registered with the tower. From 013df67402f3cfbbcd300671ce662a06dd29d58f Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 30 Aug 2022 10:03:58 +0200 Subject: [PATCH 018/119] Adds a ready signal for the http API In normal conditions, if the http API cannot bootstrap (due to the gRPC server being temporary unavailable) it will just retry until it connects. However, this is done asynchronously, meaning that it could be the case that the tower is reported as ready when it is actually not. This can be hit during E2E testing if the tower is restarted too quickly, resulting in a test failure: https://github.com/sr-gi/rust-teos/runs/8074272734?check_suite_focus=true#step:9:5543 https://github.com/sr-gi/rust-teos/runs/8074272734?check_suite_focus=true#step:9:6970 --- teos/src/api/http.rs | 10 ++++++++-- teos/src/main.rs | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 644de14..ef0afe1 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -4,7 +4,7 @@ use std::error::Error; use std::net::SocketAddr; use tokio::time::Duration; use tonic::transport::Channel; -use triggered::Listener; +use triggered::{Listener, Trigger}; use warp::{http::StatusCode, reject, reply, Filter, Rejection, Reply}; use teos_common::appointment::LOCATOR_LEN; @@ -288,7 +288,12 @@ async fn handle_rejection(err: Rejection) -> Result { } } -pub async fn serve(http_bind: SocketAddr, grpc_bind: String, shutdown_signal: Listener) { +pub async fn serve( + http_bind: SocketAddr, + grpc_bind: String, + service_ready: Trigger, + shutdown_signal: Listener, +) { let grpc_conn = loop { match PublicTowerServicesClient::connect(grpc_bind.clone()).await { Ok(conn) => break conn, @@ -300,6 +305,7 @@ pub async fn serve(http_bind: SocketAddr, grpc_bind: String, shutdown_signal: Li }; let (_, server) = warp::serve(router(grpc_conn)) .bind_with_graceful_shutdown(http_bind, async { shutdown_signal.await }); + service_ready.trigger(); server.await } diff --git a/teos/src/main.rs b/teos/src/main.rs index 789f197..1fbd843 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -283,11 +283,14 @@ async fn main() { .unwrap(); }); + let (http_service_ready, ready_signal_http) = triggered::trigger(); let http_api_task = task::spawn(http::serve( http_api_addr, internal_rpc_api_uri, + http_service_ready, shutdown_signal_http, )); + ready_signal_http.await; // Add Tor Onion Service for public API let mut tor_task = Option::None; From b2dc2e908bc5be658cab9bfda51f290875b9b403 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 30 Aug 2022 13:27:57 +0200 Subject: [PATCH 019/119] Simplifies bind_with_graceful_shutdown for http api `bind_with_graceful_shutdown` requires the shutdown signal to implement `std::future::Future`. Turns out `triggered::Listener` already does, so there is no need to wrap this in an async block. --- teos/src/api/http.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index ef0afe1..9267b7e 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -303,8 +303,8 @@ pub async fn serve( } } }; - let (_, server) = warp::serve(router(grpc_conn)) - .bind_with_graceful_shutdown(http_bind, async { shutdown_signal.await }); + let (_, server) = + warp::serve(router(grpc_conn)).bind_with_graceful_shutdown(http_bind, shutdown_signal); service_ready.trigger(); server.await } From 21c82dcc87333f7570f216982f805b9af7b96b20 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 1 Sep 2022 19:47:52 +0200 Subject: [PATCH 020/119] Fixes E2E test test_watchtower `watchtower-plugin::tests::test::test_watchtower` was not properly checking that an appointment could not be found after resolving. This was due to an off-by-one error that made the resolution not actually happening. --- watchtower-plugin/tests/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 521be61..3ae83b1 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -63,14 +63,14 @@ def test_watchtower(node_factory, bitcoind, teosd): assert l2.rpc.getappointment(tower_id, locator)["status"] == "dispute_responded" # Generate blocks until the penalty gets irrevocably resolved - for i in range(100): + for i in range(101): bitcoind.generate_block(1) time.sleep(0.1) if i < 100: assert l2.rpc.getappointment(tower_id, locator)["status"] == "dispute_responded" else: # Once the channel gets irrevocably resolved the tower will forget about it - assert l2.rpc.getappointment(tower_id, locator)["status"] == "not_found" + assert l2.rpc.getappointment(tower_id, locator) == {"error": "Appointment not found", "error_code": 36} # Make sure the penalty outputs are in l2's wallet fund_txids = [o["txid"] for o in l2.rpc.listfunds()["outputs"]] @@ -183,7 +183,7 @@ def test_get_appointment(node_factory, bitcoind, teosd, directory): bitcoind.generate_block(1) time.sleep(1) - # And after. Now this should be a tracker + # And after. Now this should be a tracker tracker = l2.rpc.getappointment(tower_id, locator)["appointment"] assert "dispute_txid" in tracker and "penalty_txid" in tracker and "penalty_rawtx" in tracker From c5f9c3a8c486ff337dd7d6d667816387ee04f614 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 29 Aug 2022 17:34:34 +0200 Subject: [PATCH 021/119] Fix #84 and revamps the retrier --- .github/workflows/cln-plugin.yaml | 2 +- watchtower-plugin/src/dbm.rs | 38 ++- watchtower-plugin/src/lib.rs | 9 +- watchtower-plugin/src/main.rs | 66 ++-- watchtower-plugin/src/retrier.rs | 456 +++++++++++++++---------- watchtower-plugin/src/wt_client.rs | 17 +- watchtower-plugin/tests/conftest.py | 11 +- watchtower-plugin/tests/pyproject.toml | 1 + watchtower-plugin/tests/test.py | 5 +- 9 files changed, 399 insertions(+), 206 deletions(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 11300c4..56ae57a 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -61,4 +61,4 @@ jobs: - name: Run tests run: | cd watchtower-plugin/tests - DEVELOPER=1 SLOW_MACHINE=1 poetry run pytest test.py -s + DEVELOPER=1 SLOW_MACHINE=1 poetry run pytest test.py --log-cli-level=INFO -s diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index a59458c..84a7398 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -405,6 +405,22 @@ impl DBM { appointments } + /// Loads an appointment from the database. + pub fn load_appointment(&self, locator: Locator) -> Result { + let mut stmt = self + .connection + .prepare("SELECT encrypted_blob, to_self_delay FROM appointments WHERE locator = ?") + .unwrap(); + + stmt.query_row(params![locator.to_vec()], |row| { + let encrypted_blob = row.get::<_, Vec>(0).unwrap(); + let to_self_delay = row.get::<_, u32>(1).unwrap(); + + Ok(Appointment::new(locator, encrypted_blob, to_self_delay)) + }) + .map_err(|_| Error::NotFound) + } + /// Stores an appointment into the database. /// /// Appointments are only stored as a whole when they are pending or invalid. @@ -1010,7 +1026,27 @@ mod tests { ); } - // `store_appointments` is implicitly tested by `store_pending_appointment` and `store_invalid_appointment` + #[test] + fn test_store_load_appointment() { + let mut dbm = DBM::in_memory().unwrap(); + + let appointment = generate_random_appointment(None); + let tx = dbm.get_mut_connection().transaction().unwrap(); + DBM::store_appointment(&tx, &appointment).unwrap(); + tx.commit().unwrap(); + + let loaded_appointment = dbm.load_appointment(appointment.locator); + assert_eq!(appointment, loaded_appointment.unwrap()); + } + + #[test] + fn test_store_load_appointment_inexistent() { + let dbm = DBM::in_memory().unwrap(); + + let locator = generate_random_appointment(None).locator; + let loaded_appointment = dbm.load_appointment(locator); + assert!(matches!(loaded_appointment, Err(Error::NotFound))); + } #[test] fn test_store_pending_appointment() { diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index e23563d..dc7804c 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -71,9 +71,14 @@ impl TowerStatus { *self == TowerStatus::Reachable } + /// Whether the tower is unreachable or not. + pub fn is_temporary_unreachable(&self) -> bool { + *self == TowerStatus::TemporaryUnreachable + } + /// Whether the tower is unreachable or not. pub fn is_unreachable(&self) -> bool { - *self == TowerStatus::TemporaryUnreachable || *self == TowerStatus::Unreachable + *self == TowerStatus::Unreachable } /// Whether the tower is misbehaving or not. @@ -411,7 +416,7 @@ mod tests { Vec::new(), ); - assert_eq!(tower_info.status, TowerStatus::Reachable); + assert!(tower_info.status.is_reachable()); assert!(tower_info.misbehaving_proof.is_none()); } diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 036350e..7419512 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -22,7 +22,7 @@ use watchtower_plugin::net::http::{ add_appointment, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, }; -use watchtower_plugin::retrier::Retrier; +use watchtower_plugin::retrier::RetryManager; use watchtower_plugin::wt_client::WTClient; use watchtower_plugin::TowerStatus; @@ -318,18 +318,26 @@ async fn retry_tower( let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; let state = plugin.state().lock().unwrap(); if let Some(tower) = state.towers.get(&tower_id) { - if tower.status == TowerStatus::TemporaryUnreachable { + if tower.status.is_temporary_unreachable() { return Err(anyhow!("{} is already being retried", tower_id)); - } else if tower.status != TowerStatus::Unreachable { + } else if !tower.status.is_unreachable() { return Err(anyhow!( "Tower status must be unreachable to manually retry", )); } - state - .unreachable_towers - .send(tower_id) - .map_err(|e| anyhow!(e))?; + for locator in state + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .iter() + { + state + .unreachable_towers + .send((tower_id, *locator)) + .map_err(|e| anyhow!(e))?; + } Ok(json!(format!("Retrying {}", tower_id))) } else { Err(anyhow!("Unknown tower {}", tower_id)) @@ -421,17 +429,27 @@ async fn on_commitment_revocation( state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); state.add_pending_appointment(tower_id, &appointment); - state.unreachable_towers.send(tower_id).unwrap(); + state + .unreachable_towers + .send((tower_id, appointment.locator)) + .unwrap(); } } AddAppointmentError::ApiError(e) => match e.error_code { errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { - log::warn!("There is a subscription issue with {}", tower_id); + log::warn!( + "There is a subscription issue with {}. Adding {} to pending", + tower_id, + appointment.locator + ); let mut state = plugin.state().lock().unwrap(); state.set_tower_status(tower_id, TowerStatus::SubscriptionError); state.add_pending_appointment(tower_id, &appointment); - state.unreachable_towers.send(tower_id).unwrap(); + state + .unreachable_towers + .send((tower_id, appointment.locator)) + .unwrap(); } _ => { @@ -466,18 +484,28 @@ async fn on_commitment_revocation( } else { if status.is_subscription_error() { log::warn!( - "There is a subscription issue with {}. Adding appointment to pending", + "There is a subscription issue with {}. Adding {} to pending", tower_id, + appointment.locator ); } else { - log::warn!("{} is {}. Adding appointment to pending", tower_id, status); + log::warn!( + "{} is {}. Adding {} to pending", + tower_id, + status, + appointment.locator, + ); } - plugin - .state() - .lock() - .unwrap() - .add_pending_appointment(tower_id, &appointment); + let mut state = plugin.state().lock().unwrap(); + state.add_pending_appointment(tower_id, &appointment); + + if status.is_temporary_unreachable() { + state + .unreachable_towers + .send((tower_id, appointment.locator)) + .unwrap(); + } } } @@ -587,8 +615,8 @@ async fn main() -> Result<(), Error> { 60 }; tokio::spawn(async move { - Retrier::new(state_clone, max_elapsed_time, max_interval_time) - .manage_retry(rx) + RetryManager::new(state_clone) + .manage_retry(max_elapsed_time, max_interval_time, rx) .await }); plugin.join().await diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 14b092a..cfc5afd 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -1,3 +1,4 @@ +use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::mpsc::UnboundedReceiver; @@ -5,167 +6,263 @@ use tokio::sync::mpsc::UnboundedReceiver; use backoff::future::retry_notify; use backoff::{Error, ExponentialBackoff}; +use teos_common::appointment::Locator; use teos_common::cryptography; use teos_common::errors; use teos_common::UserId as TowerId; use crate::net::http::{add_appointment, AddAppointmentError}; use crate::wt_client::WTClient; -use crate::AppointmentStatus; -pub struct Retrier { +pub struct RetryManager { wt_client: Arc>, - max_elapsed_time_secs: u16, - max_interval_time_secs: u16, + retriers: Arc>>, } -impl Retrier { - pub fn new( - wt_client: Arc>, - max_elapsed_time_secs: u16, - max_interval_time_secs: u16, - ) -> Self { - Self { +impl RetryManager { + pub fn new(wt_client: Arc>) -> Self { + RetryManager { wt_client, - max_elapsed_time_secs, - max_interval_time_secs, + retriers: Arc::new(Mutex::new(HashMap::new())), } } - pub async fn manage_retry(&self, mut unreachable_towers: UnboundedReceiver) { + pub async fn manage_retry( + &mut self, + max_elapsed_time_secs: u16, + max_interval_time_secs: u16, + mut unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, + ) { log::info!("Starting retry manager"); loop { - let tower_id = unreachable_towers.recv().await.unwrap(); + let (tower_id, locator) = unreachable_towers.recv().await.unwrap(); + // Not start a retry if the tower is flagged to be abandoned { - // Not start a retry if the tower has been abandoned - let mut wt_client = self.wt_client.lock().unwrap(); - if wt_client.towers.get(&tower_id).is_none() { + let wt_client = self.wt_client.lock().unwrap(); + if !wt_client.towers.contains_key(&tower_id) { log::info!("Skipping retrying abandoned tower {}", tower_id); continue; } - wt_client.set_tower_status(tower_id, crate::TowerStatus::TemporaryUnreachable); } - log::info!("Retrying tower {}", tower_id); - match retry_notify( - ExponentialBackoff { - max_elapsed_time: Some(Duration::from_secs(self.max_elapsed_time_secs as u64)), - max_interval: Duration::from_secs(self.max_interval_time_secs as u64), - ..ExponentialBackoff::default() - }, - || async { self.add_appointment(tower_id).await }, - |err, _| { - log::warn!("Retry error happened with {}. {}", tower_id, err); - }, - ) - .await - { - Ok(_) => { - log::info!("Retry strategy succeeded for {}", tower_id); - self.wt_client - .lock() - .unwrap() - .set_tower_status(tower_id, crate::TowerStatus::Reachable); - } - Err(e) => { - log::warn!("Retry strategy gave up for {}. {}", tower_id, e); - // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy - // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior) - let mut wt_client = self.wt_client.lock().unwrap(); - if let Some(tower) = wt_client.towers.get_mut(&tower_id) { - if tower.status.is_unreachable() { - log::warn!("Setting {} as unreachable", tower_id); - wt_client.set_tower_status(tower_id, crate::TowerStatus::Unreachable); + if let Some(retrier) = self.add_pending_appointment(tower_id, locator) { + log::info!("Retrying tower {}", tower_id); + let wt_client = self.wt_client.clone(); + let retriers = self.retriers.clone(); + + tokio::spawn(async move { + let r = retry_notify( + ExponentialBackoff { + max_elapsed_time: Some(Duration::from_secs( + max_elapsed_time_secs as u64, + )), + max_interval: Duration::from_secs(max_interval_time_secs as u64), + ..ExponentialBackoff::default() + }, + || async { retrier.retry_tower(tower_id).await }, + |err, _| { + log::warn!("Retry error happened with {}. {}", tower_id, err); + }, + ) + .await; + + let mut state = wt_client.lock().unwrap(); + let retrier = retriers.lock().unwrap().remove(&tower_id).unwrap(); + + match r { + Ok(_) => { + let pending_appointments = retrier.pending_appointments.lock().unwrap(); + if !pending_appointments.is_empty() { + // If there are pending appointments by the time we remove the retrier we send them back through the channel + // so they are not missed. Notice this is unlikely given the map is checked before exiting `retry_tower`, but it + // can happen. + log::info!( + "Some data was missed while retrying {}. Adding it back", + tower_id + ); + for locator in retrier.pending_appointments.lock().unwrap().drain() + { + state.unreachable_towers.send((tower_id, locator)).unwrap(); + } + } else { + log::info!("Retry strategy succeeded for {}", tower_id); + state.set_tower_status(tower_id, crate::TowerStatus::Reachable); + } + } + Err(e) => { + log::warn!("Retry strategy gave up for {}. {}", tower_id, e); + // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy + // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior) + if let Some(tower) = state.towers.get_mut(&tower_id) { + if tower.status.is_temporary_unreachable() { + log::warn!("Setting {} as unreachable", tower_id); + state.set_tower_status( + tower_id, + crate::TowerStatus::Unreachable, + ); + } + } else { + log::info!("Skipping retrying abandoned tower {}", tower_id); + } } - } else { - log::info!("Skipping retrying abandoned tower {}", tower_id); } - } + }); } } } - async fn add_appointment(&self, tower_id: TowerId) -> Result<(), Error<&'static str>> { + /// Adds an appointment to pending for a given tower. + /// + /// If the tower is not currently being retried, a new entry for it is created, otherwise, the data is appended to the existing entry. + /// + /// Returns true if a new entry is created, false otherwise. + fn add_pending_appointment(&mut self, tower_id: TowerId, locator: Locator) -> Option { + let mut retriers = self.retriers.lock().unwrap(); + if let std::collections::hash_map::Entry::Vacant(e) = retriers.entry(tower_id) { + log::debug!( + "Creating a new entry for tower {} with locator {}", + tower_id, + locator + ); + self.wt_client + .lock() + .unwrap() + .set_tower_status(tower_id, crate::TowerStatus::TemporaryUnreachable); + + let retrier = Retrier::new(self.wt_client.clone(), locator); + e.insert(retrier.clone()); + + Some(retrier) + } else { + log::debug!( + "Adding pending appointment {} to existing tower {}", + locator, + tower_id + ); + retriers + .get(&tower_id) + .unwrap() + .pending_appointments + .lock() + .unwrap() + .insert(locator); + + None + } + } +} + +#[derive(Clone)] +pub struct Retrier { + wt_client: Arc>, + pending_appointments: Arc>>, +} + +impl Retrier { + pub fn new(wt_client: Arc>, locator: Locator) -> Self { + Self { + wt_client, + pending_appointments: Arc::new(Mutex::new(HashSet::from([locator]))), + } + } + + async fn retry_tower(&self, tower_id: TowerId) -> Result<(), Error<&'static str>> { // Create a new scope so we can get all the data only locking the WTClient once. - let (appointments, net_addr, user_sk, proxy) = { + let (net_addr, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); if wt_client.towers.get(&tower_id).is_none() { return Err(Error::permanent("Tower was abandoned. Skipping retry")); } - let appointments = wt_client - .dbm - .lock() - .unwrap() - .load_appointments(tower_id, AppointmentStatus::Pending); + if self.pending_appointments.lock().unwrap().is_empty() { + return Err(Error::permanent("Tower has no data pending for retry")); + } + let net_addr = wt_client.towers.get(&tower_id).unwrap().net_addr.clone(); let user_sk = wt_client.user_sk; - (appointments, net_addr, user_sk, wt_client.proxy.clone()) + (net_addr, user_sk, wt_client.proxy.clone()) }; - for appointment in appointments { - match add_appointment( - tower_id, - &net_addr, - proxy.clone(), - &appointment, - &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), - ) - .await - { - Ok((slots, receipt)) => { - let mut wt_client = self.wt_client.lock().unwrap(); - wt_client.add_appointment_receipt( - tower_id, - appointment.locator, - slots, - &receipt, - ); - wt_client.remove_pending_appointment(tower_id, appointment.locator); - log::debug!("Response verified and data stored in the database"); - } - Err(e) => { - match e { - AddAppointmentError::RequestError(e) => { - if e.is_connection() { - log::warn!( - "{} cannot be reached. Tower will be retried later", - tower_id, - ); - return Err(Error::transient("Tower cannot be reached")); + while !self.pending_appointments.lock().unwrap().is_empty() { + let locators = self.pending_appointments.lock().unwrap().clone(); + for locator in locators.into_iter() { + let appointment = self + .wt_client + .lock() + .unwrap() + .dbm + .lock() + .unwrap() + .load_appointment(locator) + .unwrap(); + + match add_appointment( + tower_id, + &net_addr, + proxy.clone(), + &appointment, + &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), + ) + .await + { + Ok((slots, receipt)) => { + self.pending_appointments.lock().unwrap().remove(&locator); + let mut wt_client = self.wt_client.lock().unwrap(); + wt_client.add_appointment_receipt( + tower_id, + appointment.locator, + slots, + &receipt, + ); + wt_client.remove_pending_appointment(tower_id, appointment.locator); + log::debug!("Response verified and data stored in the database"); + } + Err(e) => { + match e { + AddAppointmentError::RequestError(e) => { + if e.is_connection() { + log::warn!( + "{} cannot be reached. Tower will be retried later", + tower_id, + ); + return Err(Error::transient("Tower cannot be reached")); + } } - } - AddAppointmentError::ApiError(e) => match e.error_code { - errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { - log::warn!("There is a subscription issue with {}", tower_id); - return Err(Error::transient("Subscription error")); + AddAppointmentError::ApiError(e) => match e.error_code { + errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { + log::warn!("There is a subscription issue with {}", tower_id); + return Err(Error::permanent("Subscription error")); + } + _ => { + log::warn!( + "{} rejected the appointment. Error: {}, error_code: {}", + tower_id, + e.error, + e.error_code + ); + // We need to move the appointment from pending to invalid + // Add it first to invalid and remove it from pending later so a cascade delete is not triggered + self.pending_appointments.lock().unwrap().remove(&locator); + let mut wt_client = self.wt_client.lock().unwrap(); + wt_client.add_invalid_appointment(tower_id, &appointment); + wt_client + .remove_pending_appointment(tower_id, appointment.locator); + } + }, + AddAppointmentError::SignatureError(proof) => { + log::warn!("Cannot recover known tower_id from the appointment receipt. Flagging tower as misbehaving"); + self.wt_client + .lock() + .unwrap() + .flag_misbehaving_tower(tower_id, proof); + return Err(Error::permanent("Tower misbehaved")); } - _ => { - log::warn!( - "{} rejected the appointment. Error: {}, error_code: {}", - tower_id, - e.error, - e.error_code - ); - // We need to move the appointment from pending to invalid - // Add itn first to invalid and remove it from pending later so a cascade delete is not triggered - let mut wt_client = self.wt_client.lock().unwrap(); - wt_client.add_invalid_appointment(tower_id, &appointment); - wt_client.remove_pending_appointment(tower_id, appointment.locator); - } - }, - AddAppointmentError::SignatureError(proof) => { - log::warn!("Cannot recover known tower_id from the appointment receipt. Flagging tower as misbehaving"); - self.wt_client - .lock() - .unwrap() - .flag_misbehaving_tower(tower_id, proof); - return Err(Error::permanent("Tower misbehaved")); } } } } } + Ok(()) } } @@ -193,8 +290,11 @@ mod tests { const MAX_INTERVAL_TIME: u16 = 1; impl Retrier { - fn dummy(wt_client: Arc>) -> Self { - Self::new(wt_client, 0, 0) + fn empty(wt_client: Arc>) -> Self { + Self { + wt_client, + pending_appointments: Arc::new(Mutex::new(HashSet::new())), + } } } @@ -244,11 +344,11 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - Retrier::new(wt_client_clone, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry(rx) + RetryManager::new(wt_client_clone) + .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) .await }); - tx.send(tower_id).unwrap(); + tx.send((tower_id, appointment.locator)).unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -306,37 +406,33 @@ mod tests { let max_elapsed_time = MAX_ELAPSED_TIME + 1; let task = tokio::spawn(async move { - Retrier::new(wt_client_clone, max_elapsed_time, MAX_INTERVAL_TIME) - .manage_retry(rx) + RetryManager::new(wt_client_clone) + .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) .await }); - tx.send(tower_id).unwrap(); + tx.send((tower_id, appointment.locator)).unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(max_elapsed_time as u64 / 3)).await; - assert_eq!( - wt_client - .lock() - .unwrap() - .towers - .get(&tower_id) - .unwrap() - .status, - TowerStatus::TemporaryUnreachable - ); + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .status + .is_temporary_unreachable()); // Wait until the task gives up and check again tokio::time::sleep(Duration::from_secs(max_elapsed_time as u64)).await; - assert_eq!( - wt_client - .lock() - .unwrap() - .towers - .get(&tower_id) - .unwrap() - .status, - TowerStatus::Unreachable - ); + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .status + .is_unreachable()); task.abort(); } @@ -381,11 +477,11 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - Retrier::new(wt_client_clone, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry(rx) + RetryManager::new(wt_client_clone) + .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) .await }); - tx.send(tower_id).unwrap(); + tx.send((tower_id, appointment.locator)).unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -465,11 +561,11 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - Retrier::new(wt_client_clone, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry(rx) + RetryManager::new(wt_client_clone) + .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) .await }); - tx.send(tower_id).unwrap(); + tx.send((tower_id, appointment.locator)).unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -511,13 +607,14 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - Retrier::new(wt_client_clone, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry(rx) + RetryManager::new(wt_client_clone) + .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) .await }); // Send the id and check how it gets removed - tx.send(tower_id).unwrap(); + tx.send((tower_id, generate_random_appointment(None).locator)) + .unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); @@ -525,7 +622,7 @@ mod tests { } #[tokio::test] - async fn test_add_appointment() { + async fn test_retry_tower() { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -564,13 +661,15 @@ mod tests { .json_body(json!(add_appointment_response)); }); - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; + // Since we are retrying manually, we need to add the data to pending appointments manually too + let retrier = Retrier::new(wt_client, appointment.locator); + let r = retrier.retry_tower(tower_id).await; assert_eq!(r, Ok(())); api_mock.assert(); } #[tokio::test] - async fn test_add_appointment_no_pending() { + async fn test_retry_tower_no_pending() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -588,13 +687,15 @@ mod tests { .unwrap(); // If there are no pending appointments the method will simply return - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; - - assert_eq!(r, Ok(())); + let r = Retrier::empty(wt_client).retry_tower(tower_id).await; + assert_eq!( + r, + Err(Error::permanent("Tower has no data pending for retry")) + ); } #[tokio::test] - async fn test_add_appointment_misbehaving() { + async fn test_retry_tower_misbehaving() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -632,13 +733,16 @@ mod tests { .header("content-type", "application/json") .json_body(json!(add_appointment_response)); }); - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; + + // Since we are retrying manually, we need to add the data to pending appointments manually too + let retrier = Retrier::new(wt_client, appointment.locator); + let r = retrier.retry_tower(tower_id).await; assert_eq!(r, Err(Error::permanent("Tower misbehaved"))); api_mock.assert(); } #[tokio::test] - async fn test_add_appointment_unreachable() { + async fn test_retry_tower_unreachable() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -660,12 +764,16 @@ mod tests { .lock() .unwrap() .add_pending_appointment(tower_id, &appointment); - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; + + // Since we are retrying manually, we need to add the data to pending appointments manually too + let retrier = Retrier::new(wt_client, appointment.locator); + let r = retrier.retry_tower(tower_id).await; + assert_eq!(r, Err(Error::transient("Tower cannot be reached"))); } #[tokio::test] - async fn test_add_appointment_subscription_error() { + async fn test_retry_tower_subscription_error() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -698,14 +806,17 @@ mod tests { .lock() .unwrap() .add_pending_appointment(tower_id, &appointment); - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; - assert_eq!(r, Err(Error::transient("Subscription error"))); + // Since we are retrying manually, we need to add the data to pending appointments manually too + let retrier = Retrier::new(wt_client, appointment.locator); + let r = retrier.retry_tower(tower_id).await; + + assert_eq!(r, Err(Error::permanent("Subscription error"))); api_mock.assert(); } #[tokio::test] - async fn test_add_appointment_rejected() { + async fn test_retry_tower_rejected() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -738,9 +849,10 @@ mod tests { .lock() .unwrap() .add_pending_appointment(tower_id, &appointment); - let r = Retrier::dummy(wt_client.clone()) - .add_appointment(tower_id) - .await; + + // Since we are retrying manually, we need to add the data to pending appointments manually too + let retrier = Retrier::new(wt_client.clone(), appointment.locator); + let r = retrier.retry_tower(tower_id).await; assert_eq!(r, Ok(())); api_mock.assert(); @@ -755,7 +867,7 @@ mod tests { } #[tokio::test] - async fn test_add_appointment_abandoned() { + async fn test_retry_tower_abandoned() { let (_, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -776,7 +888,7 @@ mod tests { wt_client.lock().unwrap().remove_tower(tower_id).unwrap(); // If there are no pending appointments the method will simply return - let r = Retrier::dummy(wt_client).add_appointment(tower_id).await; + let r = Retrier::empty(wt_client).retry_tower(tower_id).await; assert_eq!( r, diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 7c9aeb3..e42ac36 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -23,7 +23,7 @@ pub struct WTClient { /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers - pub unreachable_towers: UnboundedSender, + pub unreachable_towers: UnboundedSender<(TowerId, Locator)>, /// The user secret key. pub user_sk: SecretKey, /// The user identifier. @@ -33,7 +33,10 @@ pub struct WTClient { } impl WTClient { - pub async fn new(data_dir: PathBuf, unreachable_towers: UnboundedSender) -> Self { + pub async fn new( + data_dir: PathBuf, + unreachable_towers: UnboundedSender<(TowerId, Locator)>, + ) -> Self { // Create data dir if it does not exist fs::create_dir_all(&data_dir).await.unwrap_or_else(|e| { log::error!("Cannot create data dir: {:?}", e); @@ -56,8 +59,10 @@ impl WTClient { let towers = dbm.load_towers(); for (tower_id, tower) in towers.iter() { - if tower.status.is_unreachable() { - unreachable_towers.send(*tower_id).unwrap(); + if tower.status.is_temporary_unreachable() { + for locator in tower.pending_appointments.iter() { + unreachable_towers.send((*tower_id, *locator)).unwrap(); + } } } @@ -729,11 +734,11 @@ mod tests { // Check data in memory let tower_summary = wt_client.towers.get(&tower_id); assert!(tower_summary.is_some()); - assert_eq!(tower_summary.unwrap().status, TowerStatus::Misbehaving); + assert!(tower_summary.unwrap().status.is_misbehaving()); // Check data in DB let loaded_info = wt_client.load_tower_info(tower_id).unwrap(); - assert_eq!(loaded_info.status, TowerStatus::Misbehaving); + assert!(loaded_info.status.is_misbehaving()); assert_eq!(loaded_info.misbehaving_proof, Some(proof)); assert!(loaded_info.appointments.contains_key(&appointment.locator)); } diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index ef7fac8..3a97a67 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -28,8 +28,9 @@ class TeosCLI: def _call(self, method_name, *args): try: - r = subprocess.run(["teos-cli", f"--datadir={self.datadir}/teos", method_name, *args], capture_output=True, - text=True) + r = subprocess.run( + ["teos-cli", f"--datadir={self.datadir}/teos", method_name, *args], capture_output=True, text=True + ) if r.returncode != 0: result = ValueError(f"Unknown method {method_name}") else: @@ -123,3 +124,9 @@ def pytest_runtest_setup(item): pytest.skip("!DEVELOPER: {}".format(mark.args[0])) else: pytest.skip("!DEVELOPER: Requires DEVELOPER=1") + + +@pytest.fixture(scope="function", autouse=True) +def log_name(request): + # Here logging is used, you can use whatever you want to use for logs + logging.info("Starting '{}'".format(request.node.name)) diff --git a/watchtower-plugin/tests/pyproject.toml b/watchtower-plugin/tests/pyproject.toml index 56d2cd6..7ca3164 100644 --- a/watchtower-plugin/tests/pyproject.toml +++ b/watchtower-plugin/tests/pyproject.toml @@ -7,6 +7,7 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.9" +black = "^22.6.0" [tool.poetry.dev-dependencies] pytest = "^7.1.2" diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 3ae83b1..65c5331 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -111,7 +111,6 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): time.sleep(1) assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" - assert not l2.rpc.gettowerinfo(tower_id)["pending_appointments"] def test_retry_watchtower(node_factory, bitcoind, teosd): @@ -135,10 +134,10 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): # Start the tower and retry it teosd.start() l2.rpc.retrytower(tower_id) - time.sleep(2) + while l2.rpc.gettowerinfo(tower_id)["pending_appointments"]: + time.sleep(1) assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" - assert not l2.rpc.gettowerinfo(tower_id)["pending_appointments"] def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): From f6a60a9655849e7c7cf9971e28908667390226bb Mon Sep 17 00:00:00 2001 From: meryacine Date: Wed, 14 Sep 2022 16:27:28 +0200 Subject: [PATCH 022/119] Rework the retrier This is an attempt to rework the retrier logic to simplify how it works and make it less error prone. This is done by making the retry manager object responsible for both: 1- adding new retriers and extending current ones 2- removing retriers when they finish their work This way, we don't need a mutex to gaurd the retriers hashmap & we are sure there is no adding/extending retriers and removing them happending at the same time, because only the retry manager does it and not single retriers (i.e. retriers can't remove themselves from the retriers hashmap). The retry manager logic goes as follows: 1- drain the unreachable towers channel till it's empty, and store the pending appointments (locators to be exact) in the pending appointments set for each retrier. 2- remove any finished retrier (ones that succeeded and have no more pending appointments) and failed retriers (ones that failed to send their appointments). 3- start all the non-running retriers left after removing failed and finished retrieres. Retriers will signal thier status so that the retry manager could determine which retriers to keep, which to remove, and which to re-start. We also set tower as unreachable when destroying the tower's retrier and not after completing backoff. This makes it so that the tower is unreachable until its retrier is destroyed, thus manual tower retry by the user will fail with an error till the tower's retrier is destroyed. If we were to set the unreachable tower status after the backoff, then manual user retries might get discarded completely without an error because retrier set the tower state to unreachable too early thus allowing the user to perform manual retries, but if the user does manual retry, it won't get carried out, since the retry manager will remove that retrier anyway as it failed to deliver its pending appointments. --- watchtower-plugin/src/main.rs | 4 +- watchtower-plugin/src/retrier.rs | 350 +++++++++++++++++++------------ watchtower-plugin/tests/test.py | 16 +- 3 files changed, 238 insertions(+), 132 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 7419512..07c883b 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -615,8 +615,8 @@ async fn main() -> Result<(), Error> { 60 }; tokio::spawn(async move { - RetryManager::new(state_clone) - .manage_retry(max_elapsed_time, max_interval_time, rx) + RetryManager::new(state_clone, rx, max_elapsed_time, max_interval_time) + .manage_retry() .await }); plugin.join().await diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index cfc5afd..923c30f 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use tokio::sync::mpsc::UnboundedReceiver; +use tokio::sync::mpsc::{error::TryRecvError, UnboundedReceiver}; use backoff::future::retry_notify; use backoff::{Error, ExponentialBackoff}; @@ -16,97 +16,74 @@ use crate::wt_client::WTClient; pub struct RetryManager { wt_client: Arc>, - retriers: Arc>>, + unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, + max_elapsed_time_secs: u16, + max_interval_time_secs: u16, + retriers: HashMap, } impl RetryManager { - pub fn new(wt_client: Arc>) -> Self { - RetryManager { - wt_client, - retriers: Arc::new(Mutex::new(HashMap::new())), - } - } - pub async fn manage_retry( - &mut self, + pub fn new( + wt_client: Arc>, + unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, max_elapsed_time_secs: u16, max_interval_time_secs: u16, - mut unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, - ) { + ) -> Self { + RetryManager { + wt_client, + unreachable_towers, + max_elapsed_time_secs, + max_interval_time_secs, + retriers: HashMap::new(), + } + } + + /// Starts the retry manager's main logic loop. + /// This method will keep running until the `unreachable_towers` sender disconnects. + /// + /// It will receive any `(tower_id, locator)` pair and try to send the appointment with `locator` to + /// the tower with `tower_id`. This is done by spawning a tokio thread for each `tower_id` that tries + /// to send all the pending appointments. + pub async fn manage_retry(&mut self) { log::info!("Starting retry manager"); loop { - let (tower_id, locator) = unreachable_towers.recv().await.unwrap(); - // Not start a retry if the tower is flagged to be abandoned - { - let wt_client = self.wt_client.lock().unwrap(); - if !wt_client.towers.contains_key(&tower_id) { - log::info!("Skipping retrying abandoned tower {}", tower_id); - continue; + match self.unreachable_towers.try_recv() { + Ok((tower_id, locator)) => { + // Not start a retry if the tower is flagged to be abandoned + if !self + .wt_client + .lock() + .unwrap() + .towers + .contains_key(&tower_id) + { + log::info!("Skipping retrying abandoned tower {}", tower_id); + continue; + } + self.add_pending_appointment(tower_id, locator); } - } - - if let Some(retrier) = self.add_pending_appointment(tower_id, locator) { - log::info!("Retrying tower {}", tower_id); - let wt_client = self.wt_client.clone(); - let retriers = self.retriers.clone(); - - tokio::spawn(async move { - let r = retry_notify( - ExponentialBackoff { - max_elapsed_time: Some(Duration::from_secs( - max_elapsed_time_secs as u64, - )), - max_interval: Duration::from_secs(max_interval_time_secs as u64), - ..ExponentialBackoff::default() - }, - || async { retrier.retry_tower(tower_id).await }, - |err, _| { - log::warn!("Retry error happened with {}. {}", tower_id, err); - }, - ) - .await; - - let mut state = wt_client.lock().unwrap(); - let retrier = retriers.lock().unwrap().remove(&tower_id).unwrap(); - - match r { - Ok(_) => { - let pending_appointments = retrier.pending_appointments.lock().unwrap(); - if !pending_appointments.is_empty() { - // If there are pending appointments by the time we remove the retrier we send them back through the channel - // so they are not missed. Notice this is unlikely given the map is checked before exiting `retry_tower`, but it - // can happen. - log::info!( - "Some data was missed while retrying {}. Adding it back", - tower_id - ); - for locator in retrier.pending_appointments.lock().unwrap().drain() - { - state.unreachable_towers.send((tower_id, locator)).unwrap(); - } - } else { - log::info!("Retry strategy succeeded for {}", tower_id); - state.set_tower_status(tower_id, crate::TowerStatus::Reachable); - } - } - Err(e) => { - log::warn!("Retry strategy gave up for {}. {}", tower_id, e); - // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy - // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior) - if let Some(tower) = state.towers.get_mut(&tower_id) { - if tower.status.is_temporary_unreachable() { - log::warn!("Setting {} as unreachable", tower_id); - state.set_tower_status( - tower_id, - crate::TowerStatus::Unreachable, - ); - } - } else { - log::info!("Skipping retrying abandoned tower {}", tower_id); - } + Err(TryRecvError::Empty) => { + // Keep only running retriers and retriers ready to be started/re-started. + // This will remove failed ones and ones finished successfully and have no pending appointments. + // + // Note that a failed retrier could have received some new appointments to retry. In this case, we don't try to send + // them because we know that that tower is unreachable. We most likely received these new appointments while the tower + // was still flagged as temporarily unreachable when cleaning up after giving up retrying. + self.retriers.retain(|_, retrier| { + retrier.set_tower_status_if_failed(); + retrier.is_running() || retrier.should_start() + }); + // Start all the ready retriers. + for retrier in self.retriers.values() { + if retrier.should_start() { + self.start_retrying(retrier); } } - }); + // Sleep to not waste a lot of CPU cycles. + tokio::time::sleep(Duration::from_secs(1)).await; + } + Err(TryRecvError::Disconnected) => break, } } } @@ -114,76 +91,167 @@ impl RetryManager { /// Adds an appointment to pending for a given tower. /// /// If the tower is not currently being retried, a new entry for it is created, otherwise, the data is appended to the existing entry. - /// - /// Returns true if a new entry is created, false otherwise. - fn add_pending_appointment(&mut self, tower_id: TowerId, locator: Locator) -> Option { - let mut retriers = self.retriers.lock().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = retriers.entry(tower_id) { + fn add_pending_appointment(&mut self, tower_id: TowerId, locator: Locator) { + if let std::collections::hash_map::Entry::Vacant(e) = self.retriers.entry(tower_id) { log::debug!( "Creating a new entry for tower {} with locator {}", tower_id, locator ); - self.wt_client - .lock() - .unwrap() - .set_tower_status(tower_id, crate::TowerStatus::TemporaryUnreachable); - - let retrier = Retrier::new(self.wt_client.clone(), locator); - e.insert(retrier.clone()); - - Some(retrier) + e.insert(Retrier::new(self.wt_client.clone(), tower_id, locator)); } else { log::debug!( "Adding pending appointment {} to existing tower {}", locator, tower_id ); - retriers + self.retriers .get(&tower_id) .unwrap() .pending_appointments .lock() .unwrap() .insert(locator); - - None } } + + fn start_retrying(&self, retrier: &Retrier) { + log::info!("Retrying tower {}", retrier.tower_id); + retrier.start(self.max_elapsed_time_secs, self.max_interval_time_secs); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum RetrierStatus { + /// Retrier is stopped. This could happen if the retrier was never started or it started and + /// finished successfully. If a retrier is stopped and has some pending appointments, it should be + /// started/re-started, otherwise, it can be deleted safely. + Stopped, + /// Retrier is currently retrying the tower. If the retrier receives new appointments, it will + /// **try** to send them along (but it might not send them). + /// + /// If a retrier status is `Running`, then its associated tower is temporary unreachable. + Running, + /// Retrier failed retrying the tower. Should not be re-started. + /// + /// If a retrier status is `Failed`, then its associated tower is neither reachable nor temporary unreachable. + Failed, } #[derive(Clone)] pub struct Retrier { wt_client: Arc>, + tower_id: TowerId, pending_appointments: Arc>>, + status: Arc>, } impl Retrier { - pub fn new(wt_client: Arc>, locator: Locator) -> Self { + pub fn new(wt_client: Arc>, tower_id: TowerId, locator: Locator) -> Self { Self { wt_client, + tower_id, pending_appointments: Arc::new(Mutex::new(HashSet::from([locator]))), + status: Arc::new(Mutex::new(RetrierStatus::Stopped)), } } - async fn retry_tower(&self, tower_id: TowerId) -> Result<(), Error<&'static str>> { + fn has_pending_appointments(&self) -> bool { + !self.pending_appointments.lock().unwrap().is_empty() + } + + fn set_status(&self, status: RetrierStatus) { + *self.status.lock().unwrap() = status; + } + + pub fn is_running(&self) -> bool { + *self.status.lock().unwrap() == RetrierStatus::Running + } + + pub fn should_start(&self) -> bool { + // A retrier can be started/re-started if it is stopped (i.e. not running and not failed) + // and has some pending appointments. + *self.status.lock().unwrap() == RetrierStatus::Stopped && self.has_pending_appointments() + } + + pub fn start(&self, max_elapsed_time_secs: u16, max_interval_time_secs: u16) { + let retrier = self.clone(); + + // We shouldn't be retrying failed and running retriers. + debug_assert_eq!(*retrier.status.lock().unwrap(), RetrierStatus::Stopped); + + // Set the tower as temporary unreachable and the retrier status to running. + retrier + .wt_client + .lock() + .unwrap() + .set_tower_status(retrier.tower_id, crate::TowerStatus::TemporaryUnreachable); + retrier.set_status(RetrierStatus::Running); + + tokio::spawn(async move { + let r = retry_notify( + ExponentialBackoff { + max_elapsed_time: Some(Duration::from_secs(max_elapsed_time_secs as u64)), + max_interval: Duration::from_secs(max_interval_time_secs as u64), + ..ExponentialBackoff::default() + }, + || async { retrier.run().await }, + |err, _| { + log::warn!("Retry error happened with {}. {}", retrier.tower_id, err); + }, + ) + .await; + + let mut state = retrier.wt_client.lock().unwrap(); + + match r { + Ok(_) => { + log::info!("Retry strategy succeeded for {}", retrier.tower_id); + // Set the tower status now so new appointment doesn't go to the retry manager. + state.set_tower_status(retrier.tower_id, crate::TowerStatus::Reachable); + // Retrier succeeded and can be re-used by re-starting it. + retrier.set_status(RetrierStatus::Stopped); + } + Err(e) => { + // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy + // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior). + log::warn!("Retry strategy gave up for {}. {}", retrier.tower_id, e); + + // Retrier failed and should be given up on. Avoid setting the tower status until the retrier is + // deleted/dropped. This way users performing manual retry will get an error as the tower will be + // temporary unreachable. + // We don't need to set the tower status now. Any new appointments we receive will not be retried anyways. + retrier.set_status(RetrierStatus::Failed); + } + } + }); + } + + async fn run(&self) -> Result<(), Error<&'static str>> { // Create a new scope so we can get all the data only locking the WTClient once. - let (net_addr, user_sk, proxy) = { + let (tower_id, net_addr, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); - if wt_client.towers.get(&tower_id).is_none() { + if wt_client.towers.get(&self.tower_id).is_none() { return Err(Error::permanent("Tower was abandoned. Skipping retry")); } - if self.pending_appointments.lock().unwrap().is_empty() { + if !self.has_pending_appointments() { + // will this ever happen ?? + // FIXME: success/Ok() here instead so not to mark the tower as unreachable. return Err(Error::permanent("Tower has no data pending for retry")); } - let net_addr = wt_client.towers.get(&tower_id).unwrap().net_addr.clone(); + let net_addr = wt_client + .towers + .get(&self.tower_id) + .unwrap() + .net_addr + .clone(); let user_sk = wt_client.user_sk; - (net_addr, user_sk, wt_client.proxy.clone()) + (self.tower_id, net_addr, user_sk, wt_client.proxy.clone()) }; - while !self.pending_appointments.lock().unwrap().is_empty() { + while self.has_pending_appointments() { let locators = self.pending_appointments.lock().unwrap().clone(); for locator in locators.into_iter() { let appointment = self @@ -231,6 +299,10 @@ impl Retrier { AddAppointmentError::ApiError(e) => match e.error_code { errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { log::warn!("There is a subscription issue with {}", tower_id); + self.wt_client.lock().unwrap().set_tower_status( + tower_id, + crate::TowerStatus::SubscriptionError, + ); return Err(Error::permanent("Subscription error")); } _ => { @@ -265,6 +337,24 @@ impl Retrier { Ok(()) } + + /// Sets the correct tower status if the retrier status is failed. + /// + /// This method MUST be called before getting rid of a failed retrier, and has + /// no effect on non-failed retriers. + pub fn set_tower_status_if_failed(&self) { + if *self.status.lock().unwrap() == RetrierStatus::Failed { + let mut state = self.wt_client.lock().unwrap(); + if let Some(tower) = state.towers.get(&self.tower_id) { + if tower.status.is_temporary_unreachable() { + log::warn!("Setting {} as unreachable", self.tower_id); + state.set_tower_status(self.tower_id, crate::TowerStatus::Unreachable); + } + } else { + log::info!("Skipping retrying abandoned tower {}", self.tower_id); + } + } + } } #[cfg(test)] @@ -290,10 +380,12 @@ mod tests { const MAX_INTERVAL_TIME: u16 = 1; impl Retrier { - fn empty(wt_client: Arc>) -> Self { + fn empty(wt_client: Arc>, tower_id: TowerId) -> Self { Self { wt_client, + tower_id, pending_appointments: Arc::new(Mutex::new(HashSet::new())), + status: Arc::new(Mutex::new(RetrierStatus::Stopped)), } } } @@ -344,8 +436,8 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone) - .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() .await }); tx.send((tower_id, appointment.locator)).unwrap(); @@ -406,8 +498,8 @@ mod tests { let max_elapsed_time = MAX_ELAPSED_TIME + 1; let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone) - .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() .await }); tx.send((tower_id, appointment.locator)).unwrap(); @@ -477,8 +569,8 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone) - .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() .await }); tx.send((tower_id, appointment.locator)).unwrap(); @@ -561,8 +653,8 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone) - .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() .await }); tx.send((tower_id, appointment.locator)).unwrap(); @@ -607,8 +699,8 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone) - .manage_retry(MAX_ELAPSED_TIME, MAX_INTERVAL_TIME, rx) + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() .await }); @@ -662,8 +754,8 @@ mod tests { }); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, appointment.locator); - let r = retrier.retry_tower(tower_id).await; + let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let r = retrier.run().await; assert_eq!(r, Ok(())); api_mock.assert(); } @@ -687,7 +779,7 @@ mod tests { .unwrap(); // If there are no pending appointments the method will simply return - let r = Retrier::empty(wt_client).retry_tower(tower_id).await; + let r = Retrier::empty(wt_client, tower_id).run().await; assert_eq!( r, Err(Error::permanent("Tower has no data pending for retry")) @@ -735,8 +827,8 @@ mod tests { }); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, appointment.locator); - let r = retrier.retry_tower(tower_id).await; + let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let r = retrier.run().await; assert_eq!(r, Err(Error::permanent("Tower misbehaved"))); api_mock.assert(); } @@ -766,8 +858,8 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, appointment.locator); - let r = retrier.retry_tower(tower_id).await; + let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let r = retrier.run().await; assert_eq!(r, Err(Error::transient("Tower cannot be reached"))); } @@ -808,8 +900,8 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, appointment.locator); - let r = retrier.retry_tower(tower_id).await; + let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let r = retrier.run().await; assert_eq!(r, Err(Error::permanent("Subscription error"))); api_mock.assert(); @@ -851,8 +943,8 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client.clone(), appointment.locator); - let r = retrier.retry_tower(tower_id).await; + let retrier = Retrier::new(wt_client.clone(), tower_id, appointment.locator); + let r = retrier.run().await; assert_eq!(r, Ok(())); api_mock.assert(); @@ -888,7 +980,7 @@ mod tests { wt_client.lock().unwrap().remove_tower(tower_id).unwrap(); // If there are no pending appointments the method will simply return - let r = Retrier::empty(wt_client).retry_tower(tower_id).await; + let r = Retrier::empty(wt_client, tower_id).run().await; assert_eq!( r, diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 65c5331..735675f 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -128,12 +128,26 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): # Make a new payment with an unreachable tower l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) + + # The retrier manager waits 1 second before spawning new retriers for unreachable towers, + # so we need to wait a little bit until a retrier is started for our tower. + while l2.rpc.gettowerinfo(tower_id)["status"] == "temporary_unreachable": + time.sleep(1) assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] # Start the tower and retry it teosd.start() - l2.rpc.retrytower(tower_id) + + # Even though we set the max retry time to zero seconds, the retrier manager takes some time (1s) to recognize + # that the tower is unreachable. So manual retries might fail as the tower is marked as temporary unreachable. + while True: + try: + l2.rpc.retrytower(tower_id) + break + except Exception: + time.sleep(1) + while l2.rpc.gettowerinfo(tower_id)["pending_appointments"]: time.sleep(1) From 388c6431092b692c49c1772e489ca72e8cd3b7b2 Mon Sep 17 00:00:00 2001 From: meryacine Date: Mon, 8 Aug 2022 11:04:09 +0200 Subject: [PATCH 023/119] tests: Fix `with_height` may produce invalid blocks `with_height` test method might produce invalid blocks because, after #79, we can't create empty blocks any more, and some random txs might result in a not enough proof of work as in #56 --- teos/src/test_utils.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index df4684f..815f60f 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -82,25 +82,10 @@ impl Blockchain { pub fn with_height(mut self, height: usize) -> Self { self.blocks.reserve_exact(height); - let bits = BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0xff; 32])); - for i in 1..=height { - let prev_block = &self.blocks[i - 1]; - let prev_blockhash = prev_block.block_hash(); - let time = prev_block.header.time + height as u32; - let txdata = vec![get_random_tx()]; - let hashes = txdata.iter().map(|obj| obj.txid().as_hash()); - self.blocks.push(Block { - header: BlockHeader { - version: 0, - prev_blockhash, - merkle_root: bitcoin_merkle_root(hashes).unwrap().into(), - time, - bits, - nonce: 0, - }, - txdata, - }); + for _ in 1..=height { + self.generate(None); } + self } From fec3790494570446bbedad8a1ece469cd2d7dcec Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 15 Sep 2022 11:31:17 +0200 Subject: [PATCH 024/119] Fixes #119 Wraps `CloseHandle` inside `BitcoindStopper` and implements `Drop` for the latter so the server does not need to be manually stopped --- teos/src/api/http.rs | 81 ++++++++++++++++++++------------------- teos/src/api/internal.rs | 70 ++++++++++++++++----------------- teos/src/carrier.rs | 25 ++++++------ teos/src/responder.rs | 56 ++++++++++++++------------- teos/src/test_utils.rs | 83 ++++++++++++++++++++++++++++------------ teos/src/watcher.rs | 48 +++++++++++++---------- 6 files changed, 204 insertions(+), 159 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 9267b7e..9425045 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -321,7 +321,7 @@ mod test_helpers { use crate::api::internal::InternalAPI; use crate::protos::public_tower_services_server::PublicTowerServicesServer; - use crate::test_utils::{create_api_with_config, ApiConfig}; + use crate::test_utils::{create_api_with_config, ApiConfig, BitcoindStopper}; pub(crate) enum RequestBody<'a> { Jsonify(&'a str), @@ -332,8 +332,8 @@ mod test_helpers { pub(crate) async fn run_tower_in_background_with_config( api_config: ApiConfig, - ) -> (SocketAddr, Arc) { - let internal_rpc_api = create_api_with_config(api_config).await; + ) -> (SocketAddr, Arc, BitcoindStopper) { + let (internal_rpc_api, bitcoind_stopper) = create_api_with_config(api_config).await; let cloned = internal_rpc_api.clone(); let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); @@ -347,13 +347,14 @@ mod test_helpers { .unwrap(); }); - (addr, cloned) + (addr, cloned, bitcoind_stopper) } - pub(crate) async fn run_tower_in_background() -> SocketAddr { - run_tower_in_background_with_config(ApiConfig::default()) - .await - .0 + pub(crate) async fn run_tower_in_background() -> (SocketAddr, BitcoindStopper) { + let (sock_addr, _, bitcoind_stopper) = + run_tower_in_background_with_config(ApiConfig::default()).await; + + (sock_addr, bitcoind_stopper) } pub(crate) async fn check_api_error<'a>( @@ -425,7 +426,7 @@ mod tests_failures { #[tokio::test] async fn test_no_json_request_body() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error("/register", RequestBody::Body(""), server_addr).await; assert!(api_error.error.contains("EOF while parsing")); @@ -435,7 +436,7 @@ mod tests_failures { #[tokio::test] async fn test_wrong_json_request_body() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error("/register", RequestBody::DoNotJsonify(""), server_addr).await; assert!(api_error.error.contains("expected struct")); @@ -445,7 +446,7 @@ mod tests_failures { #[tokio::test] async fn test_empty_json_request_body() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error("/register", RequestBody::Jsonify(r#"{}"#), server_addr).await; assert!(api_error.error.contains("missing field")); @@ -455,7 +456,7 @@ mod tests_failures { #[tokio::test] async fn test_empty_field() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( "/register", RequestBody::Jsonify(r#"{"user_id": ""}"#), @@ -469,7 +470,7 @@ mod tests_failures { #[tokio::test] async fn test_wrong_field_hex_encoding_odd() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( "/register", RequestBody::Jsonify(r#"{"user_id": "a"}"#), @@ -483,7 +484,7 @@ mod tests_failures { #[tokio::test] async fn test_wrong_hex_encoding_character() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error("/register", RequestBody::Jsonify(r#"{"user_id": "022fa2900ed7fc07b4e8ca3ea081e846245b0497944644aa78ea0b994ac22074dZ"}"#), @@ -497,7 +498,7 @@ mod tests_failures { #[tokio::test] async fn test_wrong_field_size() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( "/register", RequestBody::Jsonify(r#"{"user_id": "aa"}"#), @@ -512,7 +513,7 @@ mod tests_failures { #[tokio::test] async fn test_wrong_field_type() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( "/register", RequestBody::DoNotJsonify(r#"{"user_id": 1}"#), @@ -527,7 +528,7 @@ mod tests_failures { #[tokio::test] async fn test_request_missing_field() { // We'll use a different endpoint here since we need a json object with more than one field - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( "/add_appointment", RequestBody::Jsonify(r#"{"signature": "aa"}"#), @@ -543,7 +544,7 @@ mod tests_failures { #[tokio::test] async fn test_empty_request_body() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let grpc_conn = PublicTowerServicesClient::connect(format!( "http://{}:{}", server_addr.ip(), @@ -558,12 +559,12 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED) + assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED); } #[tokio::test] async fn test_payload_too_large() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let grpc_conn = PublicTowerServicesClient::connect(format!( "http://{}:{}", server_addr.ip(), @@ -579,12 +580,12 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE) + assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); } #[tokio::test] async fn test_wrong_endpoint() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let grpc_conn = PublicTowerServicesClient::connect(format!( "http://{}:{}", server_addr.ip(), @@ -600,12 +601,12 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::NOT_FOUND) + assert_eq!(res.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn test_wrong_method() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let grpc_conn = PublicTowerServicesClient::connect(format!( "http://{}:{}", server_addr.ip(), @@ -620,7 +621,7 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED) + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); } } @@ -640,7 +641,7 @@ mod tests_methods { #[tokio::test] async fn test_register() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; let response = request_to_api::( "/register", @@ -655,7 +656,7 @@ mod tests_methods { #[tokio::test] async fn test_register_max_slots() { - let (server_addr, _) = + let (server_addr, _, _s) = run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION)).await; let user_id = get_random_user_id(); @@ -692,7 +693,7 @@ mod tests_methods { #[tokio::test] async fn test_register_service_unavailable() { - let (server_addr, _) = run_tower_in_background_with_config( + let (server_addr, _, _s) = run_tower_in_background_with_config( ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(), ) .await; @@ -720,7 +721,7 @@ mod tests_methods { #[tokio::test] async fn test_add_appointment() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); @@ -759,8 +760,8 @@ mod tests_methods { #[tokio::test] async fn test_add_appointment_non_registered() { - let server_addr = run_tower_in_background().await; - let (user_sk, _) = cryptography::get_random_keypair(); + let (server_addr, _s) = run_tower_in_background().await; + let (user_sk, _s) = cryptography::get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); @@ -787,7 +788,7 @@ mod tests_methods { #[tokio::test] async fn test_add_appointment_already_triggered() { // Get the InternalAPI so we can mess with the inner state - let (server_addr, internal_api) = + let (server_addr, internal_api, _s) = run_tower_in_background_with_config(ApiConfig::new(u32::MAX, DURATION)).await; // Register @@ -832,7 +833,7 @@ mod tests_methods { #[tokio::test] async fn test_add_appointment_service_unavailable() { - let (server_addr, _) = run_tower_in_background_with_config( + let (server_addr, _, _s) = run_tower_in_background_with_config( ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(), ) .await; @@ -862,7 +863,7 @@ mod tests_methods { #[tokio::test] async fn test_get_appointment() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); @@ -917,7 +918,7 @@ mod tests_methods { #[tokio::test] async fn test_get_appointment_non_registered() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // User is not registered let (user_sk, _) = cryptography::get_random_keypair(); @@ -950,7 +951,7 @@ mod tests_methods { #[tokio::test] async fn test_get_appointment_not_found() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); @@ -993,7 +994,7 @@ mod tests_methods { #[tokio::test] async fn test_get_appointment_service_unavailable() { - let (server_addr, _) = run_tower_in_background_with_config( + let (server_addr, _, _s) = run_tower_in_background_with_config( ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(), ) .await; @@ -1028,7 +1029,7 @@ mod tests_methods { #[tokio::test] async fn test_get_subscription_info() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); @@ -1064,7 +1065,7 @@ mod tests_methods { #[tokio::test] async fn test_get_subscription_info_non_registered() { - let server_addr = run_tower_in_background().await; + let (server_addr, _s) = run_tower_in_background().await; // User is not registered let (user_sk, _) = cryptography::get_random_keypair(); @@ -1092,7 +1093,7 @@ mod tests_methods { #[tokio::test] async fn test_get_subscription_info_service_unavailable() { let (user_sk, _) = cryptography::get_random_keypair(); - let (server_addr, _) = run_tower_in_background_with_config( + let (server_addr, _, _s) = run_tower_in_background_with_config( ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable(), ) .await; diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index 5c48bee..e79c427 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -394,7 +394,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_all_appointments() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let response = internal_api .get_all_appointments(Request::new(())) @@ -402,12 +402,12 @@ mod tests_private_api { .unwrap() .into_inner(); - assert!(matches!(response, msgs::GetAllAppointmentsResponse { .. })) + assert!(matches!(response, msgs::GetAllAppointmentsResponse { .. })); } #[tokio::test] async fn test_get_all_appointments_watcher() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Add data to the Watcher so we can retrieve it later on let (user_sk, user_pk) = get_random_keypair(); @@ -435,7 +435,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_all_appointments_responder() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Add data to the Responser so we can retrieve it later on internal_api @@ -457,7 +457,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_appointments() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let locator = Locator::new(get_random_tx().txid()).to_vec(); let response = internal_api @@ -471,7 +471,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_appointments_watcher() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; for i in 0..3 { // Create a dispute tx to be used for creating different dummy appointments with the same locator. @@ -521,7 +521,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_appointments_responder() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; for i in 0..3 { // Create a dispute tx to be used for creating different trackers. @@ -572,7 +572,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_tower_info_empty() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let response = internal_api .get_tower_info(Request::new(())) @@ -588,7 +588,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_tower_info() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Register a user let (user_sk, user_pk) = get_random_keypair(); @@ -627,7 +627,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_users() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let mut users = HashSet::new(); // Add a couple of users @@ -649,7 +649,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_users_empty() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let response = internal_api .get_users(Request::new(())) @@ -662,7 +662,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_user() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Register a user and get it back let (user_sk, user_pk) = get_random_keypair(); @@ -705,7 +705,7 @@ mod tests_private_api { #[tokio::test] async fn test_get_user_not_found() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Non-registered user let (_, user_pk) = get_random_keypair(); @@ -726,7 +726,7 @@ mod tests_private_api { #[tokio::test] async fn test_stop() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; assert!(!internal_api.shutdown_trigger.is_triggered()); internal_api.stop(Request::new(())).await.unwrap(); @@ -746,7 +746,7 @@ mod tests_public_api { #[tokio::test] async fn test_register() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let (_, user_pk) = get_random_keypair(); @@ -766,7 +766,7 @@ mod tests_public_api { #[tokio::test] async fn test_register_wrong_user_id() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let mut user_ids = Vec::new(); @@ -797,7 +797,7 @@ mod tests_public_api { #[tokio::test] async fn test_register_max_slots() { - let internal_api = create_api_with_config(ApiConfig::new(u32::MAX, DURATION)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(u32::MAX, DURATION)).await; let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk).to_vec(); @@ -825,7 +825,7 @@ mod tests_public_api { #[tokio::test] async fn test_register_service_unavailable() { - let internal_api = + let (internal_api, _s) = create_api_with_config(ApiConfig::new(u32::MAX, DURATION).bitcoind_unreachable()).await; let (_, user_pk) = get_random_keypair(); @@ -845,7 +845,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // User must be registered let (user_sk, user_pk) = get_random_keypair(); @@ -871,7 +871,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment_non_registered() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // User is not registered this time let (user_sk, _) = get_random_keypair(); @@ -899,7 +899,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment_not_enough_slots() { - let internal_api = create_api_with_config(ApiConfig::new(0, DURATION)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(0, DURATION)).await; // User is registered but has no slots let (user_sk, user_pk) = get_random_keypair(); @@ -928,7 +928,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment_subscription_expired() { - let internal_api = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; // User is registered but subscription is expired let (user_sk, user_pk) = get_random_keypair(); @@ -954,7 +954,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment_already_triggered() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); @@ -985,7 +985,7 @@ mod tests_public_api { #[tokio::test] async fn test_add_appointment_service_unavailable() { - let internal_api = + let (internal_api, _s) = create_api_with_config(ApiConfig::new(u32::MAX, DURATION).bitcoind_unreachable()).await; let (user_sk, _) = get_random_keypair(); @@ -1009,7 +1009,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_appointment() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // The user must be registered let (user_sk, user_pk) = get_random_keypair(); @@ -1037,12 +1037,12 @@ mod tests_public_api { assert!(matches!( response, common_msgs::GetAppointmentResponse { .. } - )) + )); } #[tokio::test] async fn test_get_appointment_non_registered() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // Add a first user to link the appointment to him let (user_sk, user_pk) = get_random_keypair(); @@ -1070,7 +1070,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_appointment_non_existent() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // The user is registered but the appointment does not exist let (user_sk, user_pk) = get_random_keypair(); @@ -1097,7 +1097,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_appointment_subscription_expired() { - let internal_api = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; // Register the user let (user_sk, user_pk) = get_random_keypair(); @@ -1125,7 +1125,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_appointment_service_unavailable() { - let internal_api = + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable()).await; let (user_sk, _) = get_random_keypair(); @@ -1148,7 +1148,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_subscription_info() { - let internal_api = create_api().await; + let (internal_api, _s) = create_api().await; // The user must be registered let (user_sk, user_pk) = get_random_keypair(); @@ -1167,12 +1167,12 @@ mod tests_public_api { assert!(matches!( response, common_msgs::GetSubscriptionInfoResponse { .. } - )) + )); } #[tokio::test] async fn test_get_subscription_info_non_registered() { - let internal_api = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; // The user is not registered let (user_sk, _) = get_random_keypair(); @@ -1195,7 +1195,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_subscription_info_expired() { - let internal_api = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, 0)).await; // The user is registered but the subscription has expired let (user_sk, user_pk) = get_random_keypair(); @@ -1219,7 +1219,7 @@ mod tests_public_api { #[tokio::test] async fn test_get_subscription_info_service_unavailable() { - let internal_api = + let (internal_api, _s) = create_api_with_config(ApiConfig::new(SLOTS, DURATION).bitcoind_unreachable()).await; let (user_sk, _) = get_random_keypair(); diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 3deeb05..5ead0d8 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -245,7 +245,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); @@ -269,7 +269,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -289,7 +289,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -311,7 +311,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -336,8 +336,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); - + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); let r = carrier.send_transaction(&tx); @@ -355,7 +354,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -407,7 +406,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -425,7 +424,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); @@ -440,7 +439,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); assert_eq!( @@ -455,7 +454,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); assert_eq!(carrier.get_block_height(&BlockHash::default()), None); @@ -468,7 +467,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); @@ -481,7 +480,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 0f61394..f2ba7a0 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -578,9 +578,9 @@ mod tests { use crate::rpc_errors; use crate::test_utils::{ create_carrier, generate_dummy_appointment_with_user, generate_uuid, get_random_breach, - get_random_tracker, get_random_tx, store_appointment_and_fks_to_db, Blockchain, - MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, START_HEIGHT, - SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + get_random_tracker, get_random_tx, store_appointment_and_fks_to_db, BitcoindStopper, + Blockchain, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, + START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, }; use teos_common::dbm::Error as DBError; @@ -645,16 +645,17 @@ mod tests { gatekeeper: Arc, dbm: Arc>, query: MockedServerQuery, - ) -> Responder { + ) -> (Responder, BitcoindStopper) { let tip = chain.tip(); - Responder::new(create_carrier(query, tip.deref().height), gatekeeper, dbm) + let (carrier, bitcoind_stopper) = create_carrier(query, tip.deref().height); + (Responder::new(carrier, gatekeeper, dbm), bitcoind_stopper) } fn init_responder_with_chain_and_dbm( mocked_query: MockedServerQuery, chain: &Blockchain, dbm: Arc>, - ) -> Responder { + ) -> (Responder, BitcoindStopper) { let gk = Gatekeeper::new( chain.get_block_count(), SLOTS, @@ -665,7 +666,7 @@ mod tests { create_responder(chain, Arc::new(gk), dbm, mocked_query) } - fn init_responder(mocked_query: MockedServerQuery) -> Responder { + fn init_responder(mocked_query: MockedServerQuery) -> (Responder, BitcoindStopper) { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); init_responder_with_chain_and_dbm(mocked_query, &chain, dbm) @@ -716,7 +717,7 @@ mod tests { // A fresh responder has no associated data let chain = Blockchain::default().with_height(START_HEIGHT); let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - let responder = + let (responder, _s) = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm.clone()); assert!(responder.is_fresh()); @@ -738,7 +739,8 @@ mod tests { } // Create a new Responder reusing the same DB and check that the data is loaded - let another_r = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + let (another_r, _) = + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); assert!(!responder.is_fresh()); assert_eq!(responder, another_r); } @@ -746,7 +748,7 @@ mod tests { #[test] fn test_handle_breach_delivered() { let start_height = START_HEIGHT as u32; - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let user_id = get_random_user_id(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); @@ -792,7 +794,7 @@ mod tests { #[test] fn test_handle_breach_rejected() { - let responder = init_responder(MockedServerQuery::Error( + let (responder, _s) = init_responder(MockedServerQuery::Error( rpc_errors::RPC_VERIFY_ERROR as i64, )); @@ -815,7 +817,7 @@ mod tests { #[test] fn test_add_tracker() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let start_height = START_HEIGHT as u32; // Add the necessary FKs in the database @@ -940,7 +942,7 @@ mod tests { // Has tracker should return true as long as the given tracker is held by the Responder. // As long as the tracker is in Responder.trackers and Responder.tx_tracker_map, the return // must be true. - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); // Add a new tracker let user_id = get_random_user_id(); @@ -970,7 +972,7 @@ mod tests { fn test_get_tracker() { // Should return a tracker as long as it exists let start_height = START_HEIGHT as u32; - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); // Store the user and the appointment in the database so we can add the tracker later on (due to FK restrictions) let user_id = get_random_user_id(); @@ -1008,7 +1010,7 @@ mod tests { #[test] fn test_check_confirmations() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let target_height = (START_HEIGHT * 2) as u32; // Unconfirmed transactions that miss a confirmation will be added to missed_confirmations (if not there) or their missed confirmation count till be increased @@ -1114,7 +1116,7 @@ mod tests { #[test] fn test_get_txs_to_rebroadcast() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let current_height = 100; let user_id = get_random_user_id(); @@ -1156,13 +1158,13 @@ mod tests { } } - assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs) + assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); } #[test] fn test_get_txs_to_rebroadcast_reorged() { // For reorged transactions this works a bit different, the dispute transaction will also be returned here - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let current_height = 100; let user_id = get_random_user_id(); @@ -1218,12 +1220,12 @@ mod tests { } // Since we have only added confirmed and reorged transactions, we should get back only the reorged ones. - assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs) + assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); } #[test] fn test_get_outdated_trackers() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); // Outdated trackers are those whose associated subscription is outdated and have not been confirmed yet (they don't have // a single confirmation). @@ -1273,7 +1275,7 @@ mod tests { fn test_rebroadcast_accepted() { // This test positive rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a // dedicated test (against bitcoind, not mocked). - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); let current_height = 100; // Add user to the database @@ -1341,7 +1343,7 @@ mod tests { fn test_rebroadcast_rejected() { // This test negative rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a // dedicated test (against bitcoind, not mocked). - let responder = init_responder(MockedServerQuery::Error( + let (responder, _s) = init_responder(MockedServerQuery::Error( rpc_errors::RPC_VERIFY_ERROR as i64, )); let current_height = 100; @@ -1408,7 +1410,7 @@ mod tests { #[test] fn test_delete_trackers_from_memory() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); // Add user to the database let user_id = get_random_user_id(); @@ -1465,7 +1467,7 @@ mod tests { #[test] fn test_delete_trackers() { - let responder = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular); // Add user to the database let user_id = get_random_user_id(); @@ -1604,7 +1606,8 @@ mod tests { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let start_height = START_HEIGHT * 2; let mut chain = Blockchain::default().with_height(start_height); - let responder = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + let (responder, _s) = + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); // block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder // is keeping track of. @@ -1847,7 +1850,8 @@ mod tests { fn test_block_disconnected() { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); - let responder = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + let (responder, _s) = + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); // Add user to the database let user_id = get_random_user_id(); diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 815f60f..61db7c7 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -14,7 +14,7 @@ use std::thread; use jsonrpc_http_server::jsonrpc_core::error::ErrorCode as JsonRpcErrorCode; use jsonrpc_http_server::jsonrpc_core::{Error as JsonRpcError, IoHandler, Params, Value}; -use jsonrpc_http_server::{Server, ServerBuilder}; +use jsonrpc_http_server::{CloseHandle, Server, ServerBuilder}; use bitcoincore_rpc::{Auth, Client as BitcoindClient}; @@ -375,16 +375,19 @@ pub(crate) enum MockedServerQuery { Error(i64), } -pub(crate) fn create_carrier(query: MockedServerQuery, height: u32) -> Carrier { +pub(crate) fn create_carrier(query: MockedServerQuery, height: u32) -> (Carrier, BitcoindStopper) { let bitcoind_mock = match query { MockedServerQuery::Regular => BitcoindMock::new(MockOptions::empty()), MockedServerQuery::Error(x) => BitcoindMock::new(MockOptions::with_error(x)), }; let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); - Carrier::new(bitcoin_cli, bitcoind_reachable, height) + ( + Carrier::new(bitcoin_cli, bitcoind_reachable, height), + bitcoind_mock.stopper, + ) } pub(crate) fn create_responder( @@ -406,20 +409,23 @@ pub(crate) async fn create_watcher( gatekeeper: Arc, bitcoind_mock: BitcoindMock, dbm: Arc>, -) -> Watcher { +) -> (Watcher, BitcoindStopper) { let last_n_blocks = get_last_n_blocks(chain, 6).await; - start_server(bitcoind_mock); + start_server(bitcoind_mock.server); let (tower_sk, tower_pk) = get_random_keypair(); let tower_id = UserId(tower_pk); - Watcher::new( - gatekeeper, - responder, - last_n_blocks, - chain.get_block_count(), - tower_sk, - tower_id, - dbm, + ( + Watcher::new( + gatekeeper, + responder, + last_n_blocks, + chain.get_block_count(), + tower_sk, + tower_id, + dbm, + ), + bitcoind_mock.stopper, ) } #[derive(Clone)] @@ -454,7 +460,9 @@ impl Default for ApiConfig { } } -pub(crate) async fn create_api_with_config(api_config: ApiConfig) -> Arc { +pub(crate) async fn create_api_with_config( + api_config: ApiConfig, +) -> (Arc, BitcoindStopper) { let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); let mut chain = Blockchain::default().with_height(START_HEIGHT); @@ -467,7 +475,7 @@ pub(crate) async fn create_api_with_config(api_config: ApiConfig) -> Arc Arc Arc { +pub(crate) async fn create_api() -> (Arc, BitcoindStopper) { create_api_with_config(ApiConfig::default()).await } + +#[derive(Clone)] +pub struct BitcoindStopper { + close_handle: CloseHandle, +} + +impl BitcoindStopper { + pub fn new(close_handle: CloseHandle) -> Self { + Self { close_handle } + } + + pub fn close_handle(&self) -> CloseHandle { + self.close_handle.clone() + } +} + +impl Drop for BitcoindStopper { + fn drop(&mut self) { + self.close_handle().close() + } +} + pub(crate) struct BitcoindMock { pub url: String, pub server: Server, + stopper: BitcoindStopper, } pub(crate) struct MockOptions { @@ -561,6 +595,7 @@ impl BitcoindMock { Self { url: format!("http://{}", server.address()), + stopper: BitcoindStopper::new(server.close_handle()), server, } } @@ -615,8 +650,8 @@ impl BitcoindMock { } } -pub(crate) fn start_server(bitcoind: BitcoindMock) { +pub(crate) fn start_server(server: Server) { thread::spawn(move || { - bitcoind.server.wait(); + server.wait(); }); } diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 5b13564..b1a1cbb 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -897,9 +897,9 @@ mod tests { use crate::test_utils::{ create_carrier, create_responder, create_watcher, generate_dummy_appointment, generate_dummy_appointment_with_user, generate_uuid, get_last_n_blocks, get_random_breach, - get_random_tx, store_appointment_and_fks_to_db, BitcoindMock, Blockchain, MockOptions, - MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, START_HEIGHT, - SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + get_random_tx, store_appointment_and_fks_to_db, BitcoindMock, BitcoindStopper, Blockchain, + MockOptions, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, + START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, }; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::dbm::Error as DBError; @@ -936,12 +936,15 @@ mod tests { } } - async fn init_watcher(chain: &mut Blockchain) -> Watcher { + async fn init_watcher(chain: &mut Blockchain) -> (Watcher, BitcoindStopper) { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); init_watcher_with_db(chain, dbm).await } - async fn init_watcher_with_db(chain: &mut Blockchain, dbm: Arc>) -> Watcher { + async fn init_watcher_with_db( + chain: &mut Blockchain, + dbm: Arc>, + ) -> (Watcher, BitcoindStopper) { let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); let gk = Arc::new(Gatekeeper::new( @@ -1100,7 +1103,7 @@ mod tests { // A fresh watcher has no associated data let mut chain = Blockchain::default().with_height(START_HEIGHT); let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - let watcher = init_watcher_with_db(&mut chain, dbm.clone()).await; + let (watcher, _s) = init_watcher_with_db(&mut chain, dbm.clone()).await; assert!(watcher.is_fresh()); let (user_sk, user_pk) = get_random_keypair(); @@ -1118,7 +1121,7 @@ mod tests { } // Create a new Responder reusing the same DB and check that the data is loaded - let another_w = init_watcher_with_db(&mut chain, dbm).await; + let (another_w, _as) = init_watcher_with_db(&mut chain, dbm).await; assert!(!another_w.is_fresh()); assert_eq!(watcher, another_w); } @@ -1129,7 +1132,7 @@ mod tests { // Not testing the update / rejection logic, since that's already covered in the Gatekeeper, just that the data makes // sense and the signature verifies. let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; let tower_pk = watcher.tower_id.0; let (_, user_pk) = get_random_keypair(); @@ -1154,7 +1157,7 @@ mod tests { async fn test_add_appointment() { let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); let tip_txs = chain.blocks.last().unwrap().txdata.clone(); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // add_appointment should add a given appointment to the Watcher given the following logic: // - if the appointment does not exist for a given user, add the appointment @@ -1290,10 +1293,11 @@ mod tests { // Transaction rejected // Update the Responder with a new Carrier - *watcher.responder.get_carrier().lock().unwrap() = create_carrier( + let (carrier, _as) = create_carrier( MockedServerQuery::Error(rpc_errors::RPC_VERIFY_ERROR as i64), chain.tip().deref().height, ); + *watcher.responder.get_carrier().lock().unwrap() = carrier; let dispute_tx = &tip_txs[tip_txs.len() - 2]; let invalid_appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; @@ -1376,7 +1380,7 @@ mod tests { #[tokio::test] async fn test_store_appointment() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Register the user let (_, user_pk) = get_random_keypair(); @@ -1437,7 +1441,7 @@ mod tests { #[tokio::test] async fn test_store_triggered_appointment() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Register the user let (_, user_pk) = get_random_keypair(); @@ -1462,10 +1466,11 @@ mod tests { // A properly formatted but invalid transaction should be rejected by the Responder // Update the Responder with a new Carrier that will reject the transaction - *watcher.responder.get_carrier().lock().unwrap() = create_carrier( + let (carrier, _as) = create_carrier( MockedServerQuery::Error(rpc_errors::RPC_VERIFY_ERROR as i64), chain.tip().deref().height, ); + *watcher.responder.get_carrier().lock().unwrap() = carrier; let dispute_tx = get_random_tx(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); @@ -1499,7 +1504,7 @@ mod tests { #[tokio::test] async fn test_get_appointment() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; let appointment = generate_dummy_appointment(None).inner; @@ -1605,7 +1610,7 @@ mod tests { async fn test_get_breaches() { let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); let txs = chain.blocks.last().unwrap().txdata.clone(); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Let's create some locators based on the transactions in the last block let mut locator_tx_map = HashMap::new(); @@ -1637,7 +1642,7 @@ mod tests { async fn test_filter_breaches() { let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 12); let txs = chain.blocks.last().unwrap().txdata.clone(); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Let's create some locators based on the transactions in the last block let mut locator_tx_map = HashMap::new(); @@ -1706,7 +1711,7 @@ mod tests { #[tokio::test] async fn test_delete_appointments_from_memory() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Add some appointments both to memory and to the database let mut to_be_deleted = HashMap::new(); @@ -1757,7 +1762,7 @@ mod tests { // TODO: This is an adaptation of Responder::test_delete_trackers, merge together once the method // is implemented using generics. let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // Delete appointments removes data from the appointments and locator_uuid_map // Add data to the map first @@ -1888,7 +1893,7 @@ mod tests { #[tokio::test] async fn test_filtered_block_connected() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // block_connected for the Watcher is used to keep track of what new transactions has been mined whose may be potential // channel breaches. @@ -2034,10 +2039,11 @@ mod tests { watcher.add_appointment(appointment.inner, sig).unwrap(); // Set the carrier response - *watcher.responder.get_carrier().lock().unwrap() = create_carrier( + let (carrier, _as) = create_carrier( MockedServerQuery::Error(rpc_errors::RPC_VERIFY_ERROR as i64), chain.tip().deref().height, ); + *watcher.responder.get_carrier().lock().unwrap() = carrier; watcher.block_connected( &chain.generate(Some(vec![dispute_tx])), @@ -2107,7 +2113,7 @@ mod tests { async fn test_block_disconnected() { let mut chain = Blockchain::default().with_height(START_HEIGHT); let start_height = START_HEIGHT as u32; - let watcher = init_watcher(&mut chain).await; + let (watcher, _s) = init_watcher(&mut chain).await; // block_disconnected for the Watcher fixes the locator cache by removing the disconnected block // and updates the last_known_block_height to the previous block height From f0f987933893a12c7a670058d83e895fd0cac452 Mon Sep 17 00:00:00 2001 From: meryacine Date: Tue, 6 Sep 2022 09:55:47 +0200 Subject: [PATCH 025/119] watchtower-plugin: get rid of unnecessary arc>, + pub dbm: DBM, /// A collection of towers the client is registered to. pub towers: HashMap, /// Queue of unreachable towers @@ -74,7 +72,7 @@ impl WTClient { WTClient { towers, unreachable_towers, - dbm: Arc::new(Mutex::new(dbm)), + dbm, user_sk, user_id, proxy: None, @@ -96,8 +94,6 @@ impl WTClient { } else { let previous_receipt = self .dbm - .lock() - .unwrap() .load_registration_receipt(tower_id, self.user_id) .unwrap(); if receipt.available_slots() <= previous_receipt.available_slots() { @@ -107,8 +103,6 @@ impl WTClient { } self.dbm - .lock() - .unwrap() .store_tower_record(tower_id, &tower_net_addr, receipt) .unwrap(); self.towers.insert( @@ -129,15 +123,12 @@ impl WTClient { &self, tower_id: TowerId, ) -> Result { - self.dbm - .lock() - .unwrap() - .load_registration_receipt(tower_id, self.user_id) + self.dbm.load_registration_receipt(tower_id, self.user_id) } /// Loads a tower record from the database. pub fn load_tower_info(&self, tower_id: TowerId) -> Result { - self.dbm.lock().unwrap().load_tower_record(tower_id) + self.dbm.load_tower_record(tower_id) } /// Sets the tower status to any of the `TowerStatus` variants. @@ -166,8 +157,6 @@ impl WTClient { tower.available_slots = available_slots; self.dbm - .lock() - .unwrap() .store_appointment_receipt(tower_id, locator, available_slots, receipt) .unwrap(); } else { @@ -184,10 +173,7 @@ impl WTClient { tower_id: TowerId, locator: Locator, ) -> Result { - self.dbm - .lock() - .unwrap() - .load_appointment_receipt(tower_id, locator) + self.dbm.load_appointment_receipt(tower_id, locator) } /// Adds a pending appointment to the tower record. @@ -196,8 +182,6 @@ impl WTClient { tower.pending_appointments.insert(appointment.locator); self.dbm - .lock() - .unwrap() .store_pending_appointment(tower_id, appointment) .unwrap(); } else { @@ -214,8 +198,6 @@ impl WTClient { tower.pending_appointments.remove(&locator); self.dbm - .lock() - .unwrap() .delete_pending_appointment(tower_id, locator) .unwrap(); } else { @@ -232,8 +214,6 @@ impl WTClient { tower.invalid_appointments.insert(appointment.locator); self.dbm - .lock() - .unwrap() .store_invalid_appointment(tower_id, appointment) .unwrap(); } else { @@ -247,11 +227,7 @@ impl WTClient { /// Flags a given tower as misbehaving, storing the misbehaving proof in the database. pub fn flag_misbehaving_tower(&mut self, tower_id: TowerId, proof: MisbehaviorProof) { if let Some(tower) = self.towers.get_mut(&tower_id) { - self.dbm - .lock() - .unwrap() - .store_misbehaving_proof(tower_id, &proof) - .unwrap(); + self.dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); tower.status = TowerStatus::Misbehaving; } else { log::error!("Cannot flag tower. Unknown tower_id: {}", tower_id); @@ -264,7 +240,7 @@ impl WTClient { pub fn remove_tower(&mut self, tower_id: TowerId) -> Result<(), DBError> { if self.towers.contains_key(&tower_id) { self.towers.remove(&tower_id); - self.dbm.lock().unwrap().remove_tower_record(tower_id) + self.dbm.remove_tower_record(tower_id) } else { Err(DBError::NotFound) } @@ -517,11 +493,7 @@ mod tests { .pending_appointments .contains(&appointment.locator)); // This bit is tested exhaustively in the DBM. - assert!(!wt_client - .dbm - .lock() - .unwrap() - .appointment_exists(appointment.locator)); + assert!(!wt_client.dbm.appointment_exists(appointment.locator)); } #[tokio::test] @@ -599,21 +571,13 @@ mod tests { .contains(&appointment.locator)); assert!(!wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_exists(appointment.locator)); + assert!(wt_client.dbm.appointment_exists(appointment.locator)); } #[tokio::test] @@ -662,14 +626,10 @@ mod tests { .contains(&appointment.locator)); assert!(!wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); @@ -688,23 +648,15 @@ mod tests { .contains(&appointment.locator)); assert!(wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Pending) .contains(&appointment.locator)); assert!(!wt_client .dbm - .lock() - .unwrap() .load_appointment_locators(another_tower_id, crate::AppointmentStatus::Invalid) .contains(&appointment.locator)); // GENERAL - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_exists(appointment.locator)); + assert!(wt_client.dbm.appointment_exists(appointment.locator)); } #[tokio::test] @@ -793,11 +745,7 @@ mod tests { registration_receipt.available_slots(), &appointment_receipt, ); - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower_id)); + assert!(wt_client.dbm.appointment_receipt_exists(locator, tower_id)); // Remove and check both the tower and the appointment wt_client.remove_tower(tower_id).unwrap(); @@ -806,11 +754,7 @@ mod tests { Err(DBError::NotFound) )); assert!(!wt_client.towers.contains_key(&tower_id)); - assert!(!wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower_id)); + assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower_id)); } #[tokio::test] @@ -860,16 +804,8 @@ mod tests { ); // Check that the data exists in both towers - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower2_id)); + assert!(wt_client.dbm.appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client.dbm.appointment_receipt_exists(locator, tower2_id)); // Remove tower1 and check that the appointment receipt can still be found for tower2 wt_client.remove_tower(tower1_id).unwrap(); @@ -878,16 +814,8 @@ mod tests { Err(DBError::NotFound) )); - assert!(!wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower1_id)); - assert!(wt_client - .dbm - .lock() - .unwrap() - .appointment_receipt_exists(locator, tower2_id)); + assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower1_id)); + assert!(wt_client.dbm.appointment_receipt_exists(locator, tower2_id)); } #[tokio::test] From 16d760d9ed22b2903e5a70d42f5bf94e440ded95 Mon Sep 17 00:00:00 2001 From: meryacine Date: Mon, 19 Sep 2022 19:03:04 +0200 Subject: [PATCH 026/119] docs: Update an outdated link --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 24140d5..e340a11 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -3,7 +3,7 @@ The tower can be installed and tested using cargo: ``` -git clone https://github.com/sr-gi/rust-teos.git +git clone https://github.com/talaia-labs/rust-teos.git cd rust-teos cargo install --path teos ``` From b0f294a17bdc2718518ec14a55b2486248196336 Mon Sep 17 00:00:00 2001 From: meryacine Date: Sun, 18 Sep 2022 10:04:19 +0200 Subject: [PATCH 027/119] teosd: Sanity check that bitcoind is running on the specified network --- teos/src/bitcoin_cli.rs | 48 ++++++++++++++++++++++++++++++++++++----- teos/src/config.rs | 9 +++++--- teos/src/main.rs | 1 + 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 8f10ea5..9407310 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -9,6 +9,8 @@ * at your option. */ +use std::convert::TryInto; +use std::io::{Error, ErrorKind}; use std::sync::Arc; use tokio::sync::Mutex; @@ -17,7 +19,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hashes::hex::ToHex; use bitcoin::{Block, Transaction}; use lightning::util::ser::Writeable; -use lightning_block_sync::http::HttpEndpoint; +use lightning_block_sync::http::{HttpEndpoint, JsonResponse}; use lightning_block_sync::rpc::RpcClient; use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource}; @@ -74,6 +76,7 @@ impl<'a> BitcoindClient<'a> { port: u16, rpc_user: &'a str, rpc_password: &'a str, + teos_network: &'a str, ) -> std::io::Result> { let http_endpoint = HttpEndpoint::for_host(host.to_owned()).with_port(port); let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password)); @@ -87,11 +90,26 @@ impl<'a> BitcoindClient<'a> { rpc_password, }; - // Test that bitcoind is reachable - match client.get_best_block_hash_and_height().await { - Ok(_) => Ok(client), - Err(e) => Err(e), + // Test that bitcoind is reachable. + let mut btc_network = client.get_chain().await?; + + // bitcoind reports "main" for "bitcoin" chain. + if &btc_network == "main" { + btc_network = String::from("bitcoin"); } + + // Assert teos runs on the same chain/network as bitcoind. + if btc_network != teos_network { + return Err(Error::new( + ErrorKind::InvalidInput, + format!( + "bitcoind is running on {} but teosd is set to run on {}", + btc_network, teos_network + ), + )); + } + + Ok(client) } /// Gets a fresh RPC client. @@ -127,4 +145,24 @@ impl<'a> BitcoindClient<'a> { rpc.call_method::("getrawtransaction", &[txid_hex]) .await } + + /// Gets bitcoind's network. + pub async fn get_chain(&self) -> std::io::Result { + // A wrapper type to extract "chain" key from getblockchaininfo JsonResponse. + struct BtcNetwork(String); + impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(BtcNetwork(self.0["chain"].as_str().unwrap().to_string())) + } + } + + // Ask the RPC client for the network bitcoind is running on. + let rpc = self.bitcoind_rpc_client.lock().await; + let btc_network = rpc + .call_method::("getblockchaininfo", &[]) + .await?; + + Ok(btc_network.0) + } } diff --git a/teos/src/config.rs b/teos/src/config.rs index 722c26b..48749f8 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -2,7 +2,6 @@ use bitcoin::network::constants::Network; use serde::Deserialize; -use std; use std::path::PathBuf; use std::str::FromStr; use structopt::StructOpt; @@ -326,7 +325,9 @@ mod tests { // Tests that the default configuration does not pass verification checks. This is on purpose so some fields are // required to be updated by the user. let mut config = Config::default(); - assert!(matches!(config.verify(), Err(ConfigError { .. }))); + assert!( + matches!(config.verify(), Err(ConfigError(e)) if e.contains("btc_rpc_user must be set")) + ); } #[test] @@ -349,7 +350,9 @@ mod tests { btc_network: "wrong_network".to_owned(), ..Default::default() }; - assert!(matches!(config.verify(), Err(ConfigError { .. }))); + assert!( + matches!(config.verify(), Err(ConfigError(e)) if e.contains("btc_network not recognized")) + ); } #[test] diff --git a/teos/src/main.rs b/teos/src/main.rs index 1fbd843..9f26bfd 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -130,6 +130,7 @@ async fn main() { conf.btc_rpc_port, &conf.btc_rpc_user, &conf.btc_rpc_password, + &conf.btc_network, ) .await { From 6461e7e72bb2110e2e683388477c155ac220535a Mon Sep 17 00:00:00 2001 From: meryacine Date: Mon, 19 Sep 2022 18:53:54 +0200 Subject: [PATCH 028/119] teosd: Normalize bitcoin chain network naming --- teos/src/bitcoin_cli.rs | 15 +++++--------- teos/src/conf_template.toml | 2 +- teos/src/config.rs | 41 ++++++++++++++++++------------------- teos/src/main.rs | 9 +++++++- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 9407310..ee6bb99 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -91,25 +91,20 @@ impl<'a> BitcoindClient<'a> { }; // Test that bitcoind is reachable. - let mut btc_network = client.get_chain().await?; - - // bitcoind reports "main" for "bitcoin" chain. - if &btc_network == "main" { - btc_network = String::from("bitcoin"); - } + let btc_network = client.get_chain().await?; // Assert teos runs on the same chain/network as bitcoind. if btc_network != teos_network { - return Err(Error::new( + Err(Error::new( ErrorKind::InvalidInput, format!( "bitcoind is running on {} but teosd is set to run on {}", btc_network, teos_network ), - )); + )) + } else { + Ok(client) } - - Ok(client) } /// Gets a fresh RPC client. diff --git a/teos/src/conf_template.toml b/teos/src/conf_template.toml index fb21dc1..5287d7f 100644 --- a/teos/src/conf_template.toml +++ b/teos/src/conf_template.toml @@ -10,7 +10,7 @@ rpc_bind = "127.0.0.1" rpc_port = 8814 # bitcoind -btc_network = "bitcoin" +btc_network = "mainnet" btc_rpc_user = "CSW" btc_rpc_password = "NotSatoshi" btc_rpc_connect = "localhost" diff --git a/teos/src/config.rs b/teos/src/config.rs index 48749f8..469c630 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -1,9 +1,7 @@ //! Logic related to the tower configuration and command line parameter parsing. -use bitcoin::network::constants::Network; use serde::Deserialize; use std::path::PathBuf; -use std::str::FromStr; use structopt::StructOpt; pub fn data_dir_absolute_path(data_dir: String) -> PathBuf { @@ -212,25 +210,26 @@ impl Config { return Err(ConfigError("btc_rpc_password must be set".to_owned())); } - match Network::from_str(&self.btc_network) { - Ok(network) => { - // Set the port to it's default (depending on the network) if it has not been - // overwritten at this point. - if self.btc_rpc_port == 0 { - self.btc_rpc_port = match network { - Network::Testnet => 18332, - Network::Signet => 38332, - Network::Regtest => 18443, - _ => 8332, - } - } - Ok(()) - } - Err(_) => { - Err(ConfigError(format!("btc_network not recognized. Expected {{bitcoin, testnet, signet, regtest}}, received {}", - self.btc_network))) - } + // Normalize the network option to the ones used by bitcoind. + if ["mainnet", "testnet"].contains(&self.btc_network.as_str()) { + self.btc_network = self.btc_network.trim_end_matches("net").into(); } + + let default_rpc_port = match self.btc_network.as_str() { + "main" => 8332, + "test" => 18332, + "regtest" => 18443, + "signet" => 38332, + _ => return Err(ConfigError(format!("btc_network not recognized. Expected {{mainnet, testnet, signet, regtest}}, received {}", self.btc_network))) + }; + + // Set the port to it's default (depending on the network) if it has not been + // overwritten at this point. + if self.btc_rpc_port == 0 { + self.btc_rpc_port = default_rpc_port; + } + + Ok(()) } /// Checks whether the config has been set with only with default values. @@ -255,7 +254,7 @@ impl Default for Config { onion_hidden_service_port: 2121, rpc_bind: "127.0.0.1".into(), rpc_port: 8814, - btc_network: "bitcoin".into(), + btc_network: "mainnet".into(), btc_rpc_user: String::new(), btc_rpc_password: String::new(), btc_rpc_connect: "localhost".into(), diff --git a/teos/src/main.rs b/teos/src/main.rs index 9f26bfd..afde95b 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -177,7 +177,14 @@ async fn main() { }; log::info!("Last known block: {}", tip.header.block_hash()); - let mut poller = ChainPoller::new(&mut derefed, Network::from_str(&conf.btc_network).unwrap()); + // This is how chain poller names bitcoin networks. + let btc_network = match conf.btc_network.as_str() { + "main" => "bitcoin", + "test" => "testnet", + any => any, + }; + + let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); let last_n_blocks = get_last_n_blocks(&mut poller, tip, 6).await; // Build components From 35a15b14eb45306f92258ac620e3c324eda0664a Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 19 Sep 2022 20:10:24 +0000 Subject: [PATCH 029/119] fix: Arc retriers so we reduce the cloning + antipatern One thing I really disliked about #89 was having a method that cloned its caller to be able to work around spawning a task inside it that called a method of the same class. Turns out you can have self as Arc which would completely prevent having to do such a thing, plus it also reduces the number of things being cloned. --- watchtower-plugin/src/retrier.rs | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index fb9e1e6..51f6b14 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -19,7 +19,7 @@ pub struct RetryManager { unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, max_elapsed_time_secs: u16, max_interval_time_secs: u16, - retriers: HashMap, + retriers: HashMap>, } impl RetryManager { @@ -77,7 +77,7 @@ impl RetryManager { // Start all the ready retriers. for retrier in self.retriers.values() { if retrier.should_start() { - self.start_retrying(retrier); + self.start_retrying(retrier.clone()); } } // Sleep to not waste a lot of CPU cycles. @@ -98,7 +98,11 @@ impl RetryManager { tower_id, locator ); - e.insert(Retrier::new(self.wt_client.clone(), tower_id, locator)); + e.insert(Arc::new(Retrier::new( + self.wt_client.clone(), + tower_id, + locator, + ))); } else { log::debug!( "Adding pending appointment {} to existing tower {}", @@ -115,7 +119,7 @@ impl RetryManager { } } - fn start_retrying(&self, retrier: &Retrier) { + fn start_retrying(&self, retrier: Arc) { log::info!("Retrying tower {}", retrier.tower_id); retrier.start(self.max_elapsed_time_secs, self.max_interval_time_secs); } @@ -138,12 +142,11 @@ pub enum RetrierStatus { Failed, } -#[derive(Clone)] pub struct Retrier { wt_client: Arc>, tower_id: TowerId, - pending_appointments: Arc>>, - status: Arc>, + pending_appointments: Mutex>, + status: Mutex, } impl Retrier { @@ -151,8 +154,8 @@ impl Retrier { Self { wt_client, tower_id, - pending_appointments: Arc::new(Mutex::new(HashSet::from([locator]))), - status: Arc::new(Mutex::new(RetrierStatus::Stopped)), + pending_appointments: Mutex::new(HashSet::from([locator])), + status: Mutex::new(RetrierStatus::Stopped), } } @@ -174,19 +177,16 @@ impl Retrier { *self.status.lock().unwrap() == RetrierStatus::Stopped && self.has_pending_appointments() } - pub fn start(&self, max_elapsed_time_secs: u16, max_interval_time_secs: u16) { - let retrier = self.clone(); - + pub fn start(self: Arc, max_elapsed_time_secs: u16, max_interval_time_secs: u16) { // We shouldn't be retrying failed and running retriers. - debug_assert_eq!(*retrier.status.lock().unwrap(), RetrierStatus::Stopped); + debug_assert_eq!(*self.status.lock().unwrap(), RetrierStatus::Stopped); // Set the tower as temporary unreachable and the retrier status to running. - retrier - .wt_client + self.wt_client .lock() .unwrap() - .set_tower_status(retrier.tower_id, crate::TowerStatus::TemporaryUnreachable); - retrier.set_status(RetrierStatus::Running); + .set_tower_status(self.tower_id, crate::TowerStatus::TemporaryUnreachable); + self.set_status(RetrierStatus::Running); tokio::spawn(async move { let r = retry_notify( @@ -195,33 +195,33 @@ impl Retrier { max_interval: Duration::from_secs(max_interval_time_secs as u64), ..ExponentialBackoff::default() }, - || async { retrier.run().await }, + || async { self.run().await }, |err, _| { - log::warn!("Retry error happened with {}. {}", retrier.tower_id, err); + log::warn!("Retry error happened with {}. {}", self.tower_id, err); }, ) .await; - let mut state = retrier.wt_client.lock().unwrap(); + let mut state = self.wt_client.lock().unwrap(); match r { Ok(_) => { - log::info!("Retry strategy succeeded for {}", retrier.tower_id); + log::info!("Retry strategy succeeded for {}", self.tower_id); // Set the tower status now so new appointment doesn't go to the retry manager. - state.set_tower_status(retrier.tower_id, crate::TowerStatus::Reachable); + state.set_tower_status(self.tower_id, crate::TowerStatus::Reachable); // Retrier succeeded and can be re-used by re-starting it. - retrier.set_status(RetrierStatus::Stopped); + self.set_status(RetrierStatus::Stopped); } Err(e) => { // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior). - log::warn!("Retry strategy gave up for {}. {}", retrier.tower_id, e); + log::warn!("Retry strategy gave up for {}. {}", self.tower_id, e); // Retrier failed and should be given up on. Avoid setting the tower status until the retrier is // deleted/dropped. This way users performing manual retry will get an error as the tower will be // temporary unreachable. // We don't need to set the tower status now. Any new appointments we receive will not be retried anyways. - retrier.set_status(RetrierStatus::Failed); + self.set_status(RetrierStatus::Failed); } } }); @@ -382,8 +382,8 @@ mod tests { Self { wt_client, tower_id, - pending_appointments: Arc::new(Mutex::new(HashSet::new())), - status: Arc::new(Mutex::new(RetrierStatus::Stopped)), + pending_appointments: Mutex::new(HashSet::new()), + status: Mutex::new(RetrierStatus::Stopped), } } } From b6e223a4ed009ecbb2c88261e65f7168d7515086 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Sat, 17 Sep 2022 15:22:20 +0200 Subject: [PATCH 030/119] Bumps version to 0.1.2 Makes it so the .rs files pull the version info from the Cargo files so it is always consistent --- teos-common/Cargo.toml | 2 +- teos/Cargo.toml | 2 +- teos/src/cli_config.rs | 2 +- teos/src/config.rs | 2 +- watchtower-plugin/Cargo.toml | 2 +- watchtower-plugin/tests/pyproject.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 18bfabc..0ee03cf 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teos-common" -version = "0.0.1" +version = "0.1.2" authors = ["Sergi Delgado Segura "] edition = "2018" diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 30dafea..2e38098 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teos" -version = "0.0.1" +version = "0.1.2" authors = ["Sergi Delgado Segura "] license = "MIT" edition = "2018" diff --git a/teos/src/cli_config.rs b/teos/src/cli_config.rs index 4f93720..60adc5c 100644 --- a/teos/src/cli_config.rs +++ b/teos/src/cli_config.rs @@ -37,7 +37,7 @@ pub struct GetAppointmentsData { #[derive(StructOpt, Debug)] #[structopt(rename_all = "lowercase")] #[structopt( - version = "0.0.1", + version = env!("CARGO_PKG_VERSION"), about = "The Eye of Satoshi - CLI", name = "teos-cli" )] diff --git a/teos/src/config.rs b/teos/src/config.rs index 469c630..ec43d57 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -44,7 +44,7 @@ impl std::error::Error for ConfigError {} /// Holds all the command line options. #[derive(StructOpt, Debug, Clone)] #[structopt(rename_all = "lowercase")] -#[structopt(version = "0.0.1", about = "The Eye of Satoshi - Lightning watchtower")] +#[structopt(version = env!("CARGO_PKG_VERSION"), about = "The Eye of Satoshi - Lightning watchtower")] pub struct Opt { /// Address teos HTTP(s) API will bind to [default: localhost] #[structopt(long)] diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index df19470..ae864f2 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "watchtower-plugin" -version = "0.1.0" +version = "0.1.2" authors = ["Sergi Delgado Segura "] license = "MIT" edition = "2018" diff --git a/watchtower-plugin/tests/pyproject.toml b/watchtower-plugin/tests/pyproject.toml index 7ca3164..c81b8b0 100644 --- a/watchtower-plugin/tests/pyproject.toml +++ b/watchtower-plugin/tests/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tests" -version = "0.1.0" +version = "0.1.2" description = "watchtower-plugin tests" authors = ["Sergi Delgado Segura "] license = "MIT" From 81334d739e97d5bacfa08ce8cdc053a9a3ba85a0 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 21 Sep 2022 09:31:53 +0000 Subject: [PATCH 031/119] Fixes some wording in the Tor section of the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87be677..fa7d4b0 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ CookieAuthentication 1 CookieAuthFileGroupReadable 1 ``` -Once the Tor daemon is running, and the control port is open, make sure to enable the `tor_support` flag `teosd`. +Once the Tor daemon is running, and the control port is open, make sure to enable `--torsupport` when running `teosd`. ### Tower id and signing key From b1b1d83659b679d7fa545b3c4db0aaa04a96a4bf Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 21 Sep 2022 10:45:04 +0000 Subject: [PATCH 032/119] Removes chunked-transfer from deps chunked-transfer was used in an old version of the test suite for a custom HTTP server. The test server was removed in bbd3857c70e321f1a563036c11deb184d439fb3e, hence the dependency was never used from that point on. --- teos/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 2e38098..4f25c94 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -47,7 +47,6 @@ teos-common = { path = "../teos-common" } tonic-build = "0.6" [dev-dependencies] -chunked_transfer = "1.4" jsonrpc-http-server = "17.1.0" rand = "0.8.4" tempdir = "0.3.7" From fc9145b1b76cb3d542ccf7016645ff8782df7d7d Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 1 Nov 2022 15:17:04 +0100 Subject: [PATCH 033/119] Change log level for failed post requests in CoreLN plugin Let's be less dramatic, specially given connection timeouts fall under this category. --- watchtower-plugin/src/net/http.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index a57a2a4..9cb17e4 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -147,7 +147,7 @@ pub async fn post_request( }; client.post(endpoint).json(&data).send().await.map_err(|e| { - log::error!("{:?}", e); + log::debug!("POST request failed: {:?}", e); if e.is_connect() | e.is_timeout() { RequestError::ConnectionError("Cannot connect to the tower. Connection refused".into()) } else { From 0ca6f80149d3f27c35f8de0a53cef6ecac6969e0 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 28 Oct 2022 14:33:40 +0200 Subject: [PATCH 034/119] Splits log levels into local and third party libs The log level was setup equally between local code and third party libs, making really verbose libs log a lot of unnecessary data when setting loglevel to debug for instance. This splits the loglevel config option into two: `debug` and `depsdebug`. The latter covers third party libs. --- teos/src/conf_template.toml | 1 + teos/src/config.rs | 8 ++++++++ teos/src/main.rs | 24 ++++++++++++++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/teos/src/conf_template.toml b/teos/src/conf_template.toml index 5287d7f..dea1b7b 100644 --- a/teos/src/conf_template.toml +++ b/teos/src/conf_template.toml @@ -18,6 +18,7 @@ btc_rpc_port = 8332 # Flags debug = false +deps_debug = false overwrite_key = false # General diff --git a/teos/src/config.rs b/teos/src/config.rs index ec43d57..6142513 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -90,6 +90,10 @@ pub struct Opt { #[structopt(long)] pub debug: bool, + /// Runs third party libs in debug mode + #[structopt(long)] + pub deps_debug: bool, + /// Overwrites the tower secret key. THIS IS IRREVERSIBLE AND WILL CHANGE YOUR TOWER ID #[structopt(long)] pub overwrite_key: bool, @@ -133,6 +137,7 @@ pub struct Config { // Flags pub debug: bool, + pub deps_debug: bool, pub overwrite_key: bool, // General @@ -191,6 +196,7 @@ impl Config { self.tor_support |= options.tor_support; self.debug |= options.debug; + self.deps_debug |= options.deps_debug; self.overwrite_key = options.overwrite_key; } @@ -261,6 +267,7 @@ impl Default for Config { btc_rpc_port: 0, debug: false, + deps_debug: false, overwrite_key: false, subscription_slots: 10000, subscription_duration: 4320, @@ -295,6 +302,7 @@ mod tests { data_dir: String::from("~/.teos"), debug: false, + deps_debug: false, overwrite_key: false, } } diff --git a/teos/src/main.rs b/teos/src/main.rs index afde95b..f0bb9a3 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -1,4 +1,5 @@ -use simple_logger::init_with_level; +use log::LevelFilter; +use simple_logger::SimpleLogger; use std::fs; use std::io::ErrorKind; use std::ops::{Deref, DerefMut}; @@ -83,11 +84,22 @@ async fn main() { }); // Set log level - if conf.debug { - init_with_level(log::Level::Debug).unwrap() - } else { - init_with_level(log::Level::Info).unwrap() - } + SimpleLogger::new() + .with_level(if conf.deps_debug { + LevelFilter::Debug + } else { + LevelFilter::Warn + }) + .with_module_level( + "teos", + if conf.debug { + LevelFilter::Debug + } else { + LevelFilter::Info + }, + ) + .init() + .unwrap(); if is_default { log::info!("Loading default configuration") From 22dc24c561c7e2d9c9149acdcf503e1f56868211 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 28 Oct 2022 14:39:26 +0200 Subject: [PATCH 035/119] Removes debug from teos-cli teos-cli is not using logging, so the debug flag was useless. --- teos/src/cli_config.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/teos/src/cli_config.rs b/teos/src/cli_config.rs index 60adc5c..0a942db 100644 --- a/teos/src/cli_config.rs +++ b/teos/src/cli_config.rs @@ -54,10 +54,6 @@ pub struct Opt { #[structopt(long, default_value = "~/.teos")] pub data_dir: String, - /// Runs teos-cli in debug mode [default: false] - #[structopt(long)] - pub debug: bool, - /// Command #[structopt(subcommand)] pub command: Command, @@ -74,7 +70,6 @@ pub struct Opt { pub struct Config { pub rpc_bind: String, pub rpc_port: u16, - pub debug: bool, } impl Config { @@ -86,8 +81,6 @@ impl Config { if options.rpc_port.is_some() { self.rpc_port = options.rpc_port.unwrap(); } - - self.debug |= options.debug; } } @@ -102,7 +95,6 @@ impl Default for Config { Self { rpc_bind: "localhost".into(), rpc_port: 8814, - debug: false, } } } From 2765a4ce93cd10f6e67b24f80419b9e176d8acef Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 4 Nov 2022 13:26:33 +0100 Subject: [PATCH 036/119] Updates actions --- .github/workflows/build.yaml | 4 ++-- .github/workflows/cln-plugin.yaml | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a2e3aca..b1b91fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Checkout source code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust ${{ matrix.toolchain }} toolchain uses: actions-rs/toolchain@v1 with: @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust stable toolchain uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 56ae57a..6550dd3 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -10,8 +10,11 @@ jobs: cache-cln: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + check-latest: true - name: Create CLN cache id: cache-cln uses: actions/cache@v3 @@ -21,6 +24,8 @@ jobs: path: lightning key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} - name: Compile CLN + env: + PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }} run: | sudo apt-get update && sudo apt-get install gettext @@ -32,8 +37,11 @@ jobs: needs: cache-cln runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + check-latest: true - name: Install bitcoind run: | wget https://bitcoincore.org/bin/bitcoin-core-${{ env.bitcoind_version }}/bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz From 8e44159dd6a8d90fe2d592a8a745dd715d261493 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 4 Nov 2022 13:44:17 +0100 Subject: [PATCH 037/119] Fixes an uncaught clippy issue prior to rust 1.65.0 --- teos/src/extended_appointment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index b39db0f..88920de 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -22,7 +22,7 @@ impl UUID { /// Therefore, it provides a hard-to-forge id while reducing the tower lookups and the required data to be stored (no reverse maps). pub fn new(locator: Locator, user_id: UserId) -> Self { let mut uuid_data = locator.to_vec(); - uuid_data.extend(&user_id.0.serialize()); + uuid_data.extend(user_id.0.serialize()); UUID(ripemd160::Hash::hash(&uuid_data).into_inner()) } From 770b6e6db72a95de71f2b4776a8add82b6cdc251 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 28 Sep 2022 18:38:08 +0000 Subject: [PATCH 038/119] Generalizes LocatorCache into a TxIndex In order to fix #130 we need to implement our own txindex. Turns out this is almost identical to our `LocatorCache`, so we can generalize it and use it for both purposes. --- teos/src/lib.rs | 1 + teos/src/main.rs | 2 +- teos/src/test_utils.rs | 2 +- teos/src/tx_index.rs | 401 +++++++++++++++++++++++++++++++++++++++++ teos/src/watcher.rs | 263 ++------------------------- 5 files changed, 415 insertions(+), 254 deletions(-) create mode 100644 teos/src/tx_index.rs diff --git a/teos/src/lib.rs b/teos/src/lib.rs index e1073ea..2e87bcf 100644 --- a/teos/src/lib.rs +++ b/teos/src/lib.rs @@ -22,6 +22,7 @@ pub mod responder; #[doc(hidden)] mod rpc_errors; pub mod tls; +mod tx_index; pub mod watcher; #[cfg(test)] diff --git a/teos/src/main.rs b/teos/src/main.rs index f0bb9a3..de9f412 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -213,7 +213,7 @@ async fn main() { let watcher = Arc::new(Watcher::new( gatekeeper.clone(), responder.clone(), - last_n_blocks, + &last_n_blocks, tip.height, tower_sk, TowerId(tower_pk), diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 61db7c7..8d78e4d 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -419,7 +419,7 @@ pub(crate) async fn create_watcher( Watcher::new( gatekeeper, responder, - last_n_blocks, + &last_n_blocks, chain.get_block_count(), tower_sk, tower_id, diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs new file mode 100644 index 0000000..1ff7052 --- /dev/null +++ b/teos/src/tx_index.rs @@ -0,0 +1,401 @@ +use std::collections::{HashMap, VecDeque}; +use std::fmt; +use std::hash::Hash; + +use bitcoin::hash_types::BlockHash; +use bitcoin::{BlockHeader, Transaction, Txid}; +use lightning_block_sync::poll::ValidatedBlock; + +use teos_common::appointment::Locator; + +/// A trait implemented by types that can be used as key in a [TxIndex]. +pub trait Key: Hash { + fn from_txid(txid: Txid) -> Self; +} + +impl Key for Txid { + fn from_txid(txid: Txid) -> Self { + txid + } +} + +impl Key for Locator { + fn from_txid(txid: Txid) -> Self { + Locator::new(txid) + } +} + +pub enum Type { + Transaction, + BlockHash, +} + +pub enum Data { + Transaction(Transaction), + BlockHash(BlockHash), +} + +impl fmt::Display for Data { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Data::Transaction(_) => write!(f, "Transaction"), + Data::BlockHash(_) => write!(f, "BlockHash"), + } + } +} + +/// A trait implemented by types that can be used as value in a [TxIndex]. +pub trait Value { + fn get_type() -> Type; + fn from_data(d: Data) -> Self; +} + +impl Value for BlockHash { + fn get_type() -> Type { + Type::BlockHash + } + + fn from_data(d: Data) -> Self { + match d { + Data::BlockHash(b) => b, + other => panic!("Cannot build a BlockHash from {}", other), + } + } +} + +impl Value for Transaction { + fn get_type() -> Type { + Type::Transaction + } + + fn from_data(d: Data) -> Self { + match d { + Data::Transaction(t) => t, + other => panic!("Cannot build a BlockHash from {}", other), + } + } +} + +/// Data structure used to index locators computed from parsed blocks. +/// +/// Holds up to `size` blocks with their corresponding computed [Locator]s. +#[derive(Debug)] +pub struct TxIndex { + /// A [K]:[V] map. + index: HashMap, + /// Vector of block hashes covered by the index. + blocks: VecDeque, + /// Map of [BlockHash]:[Vec]. Used to remove data from the index. + tx_in_block: HashMap>, + /// The height of the last block included in the index. + tip: u32, + /// Maximum size of the index. + size: usize, +} + +impl TxIndex +where + K: Key + std::cmp::Eq + Copy, + V: Value + Clone, + Self: Sized, +{ + pub fn new(last_n_blocks: &[ValidatedBlock], height: u32) -> Self { + let size = last_n_blocks.len(); + let mut tx_index = Self { + index: HashMap::new(), + blocks: VecDeque::with_capacity(size), + tx_in_block: HashMap::new(), + tip: height, + size, + }; + + for block in last_n_blocks.iter().rev() { + if let Some(prev_block_hash) = tx_index.blocks.back() { + if block.header.prev_blockhash != *prev_block_hash { + panic!("last_n_blocks contains unchained blocks"); + } + }; + + let map = block + .txdata + .iter() + .map(|tx| { + ( + K::from_txid(tx.txid()), + match V::get_type() { + Type::Transaction => V::from_data(Data::Transaction(tx.clone())), + Type::BlockHash => { + V::from_data(Data::BlockHash(block.header.block_hash())) + } + }, + ) + }) + .collect(); + + tx_index.update(block.header, &map); + } + + tx_index + } + + /// Gets an item from the index if present. [None] otherwise. + pub fn get<'a>(&'a self, k: &'a K) -> Option<&V> { + self.index.get(k) + } + + /// Checks whether the index contains a certain key. + pub fn contains_key(&self, k: &K) -> bool { + self.index.contains_key(k) + } + + /// Checks if the index if full. + pub fn is_full(&self) -> bool { + self.blocks.len() > self.size + } + + /// Get's the height of a given block based on its position in the block queue. + pub fn get_height(&self, block_hash: &BlockHash) -> Option { + let pos = self.blocks.iter().position(|x| x == block_hash)?; + Some(self.tip as usize + pos + 1 - self.blocks.len()) + } + + /// Updates the index by adding data from a new block. Removes the oldest block if the index is full afterwards. + pub fn update(&mut self, block_header: BlockHeader, data: &HashMap) { + self.blocks.push_back(block_header.block_hash()); + + let ks = data + .iter() + .map(|(k, v)| { + self.index.insert(*k, v.clone()); + *k + }) + .collect(); + + self.tx_in_block.insert(block_header.block_hash(), ks); + + if self.is_full() { + // Avoid logging during bootstrap + log::info!("New block added to index: {}", block_header.block_hash()); + self.tip += 1; + self.remove_oldest_block(); + } + } + + /// Fixes the index by removing disconnected data. + pub fn remove_disconnected_block(&mut self, block_hash: &BlockHash) { + if let Some(ks) = self.tx_in_block.remove(block_hash) { + self.index.retain(|k, _| !ks.contains(k)); + + // Blocks should be disconnected from last backwards. Log if that's not the case so we can revisit this and fix it. + if let Some(ref h) = self.blocks.pop_back() { + if h != block_hash { + log::error!("Disconnected block does not match the oldest block stored in the TxIndex ({} != {})", block_hash, h); + } + } + } else { + log::warn!("The index is already empty"); + } + } + + /// Removes the oldest block from the index. + /// This removes data from `self.blocks`, `self.tx_in_block` and `self.index`. + pub fn remove_oldest_block(&mut self) { + let h = self.blocks.pop_front().unwrap(); + let ks = self.tx_in_block.remove(&h).unwrap(); + self.index.retain(|k, _| !ks.contains(k)); + + log::info!("Oldest block removed from index: {}", h); + } +} + +impl fmt::Display for TxIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "index: {:?}\n\nblocks: {:?}\n\ntx_in_block: {:?}\n\nsize: {}", + self.index, self.blocks, self.tx_in_block, self.size + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ops::Deref; + + use crate::test_utils::{get_last_n_blocks, Blockchain}; + + use bitcoin::Block; + + impl TxIndex + where + K: Key + std::cmp::Eq + Copy, + V: Value + Clone, + Self: Sized, + { + pub fn index_mut(&mut self) -> &mut HashMap { + &mut self.index + } + + pub fn blocks(&self) -> &VecDeque { + &self.blocks + } + } + + #[tokio::test] + async fn test_new() { + let height = 10; + let mut chain = Blockchain::default().with_height(height as usize); + let last_six_blocks = get_last_n_blocks(&mut chain, 6).await; + let blocks: Vec = last_six_blocks + .iter() + .map(|block| block.deref().clone()) + .collect(); + + let cache: TxIndex = TxIndex::new(&last_six_blocks, height); + assert_eq!(blocks.len(), cache.size); + for block in blocks.iter() { + assert!(cache.blocks().contains(&block.block_hash())); + + let mut locators = Vec::new(); + for tx in block.txdata.iter() { + let locator = Locator::new(tx.txid()); + assert!(cache.contains_key(&locator)); + locators.push(locator); + } + + assert_eq!(cache.tx_in_block[&block.block_hash()], locators); + } + } + + #[tokio::test] + async fn test_get_height() { + let cache_size = 10; + let height = 50; + let mut chain = Blockchain::default().with_height_and_txs(height, 42); + let last_n_blocks = get_last_n_blocks(&mut chain, cache_size).await; + + // last_n_blocks is ordered from latest to earliest + let first_block = last_n_blocks.get(cache_size - 1).unwrap(); + let last_block = last_n_blocks.get(0).unwrap(); + let mid = last_n_blocks.get(cache_size / 2).unwrap(); + + let cache: TxIndex = TxIndex::new(&last_n_blocks, height as u32); + + assert_eq!( + cache.get_height(&first_block.block_hash()).unwrap(), + height - cache_size + 1 + ); + assert_eq!(cache.get_height(&last_block.block_hash()).unwrap(), height); + assert_eq!( + cache.get_height(&mid.block_hash()).unwrap(), + height - cache_size / 2 + ); + } + + #[tokio::test] + async fn test_get_height_not_found() { + let cache_size = 10; + let height = 50; + let mut chain = Blockchain::default().with_height_and_txs(height, 42); + let cache: TxIndex = TxIndex::new( + &get_last_n_blocks(&mut chain, cache_size).await, + height as u32, + ); + + let fake_hash = BlockHash::default(); + assert!(matches!(cache.get_height(&fake_hash), None)); + } + + #[tokio::test] + async fn test_update() { + let height = 10; + let mut chain = Blockchain::default().with_height(height as usize); + let mut last_n_blocks = get_last_n_blocks(&mut chain, 7).await; + + // Store the last block to use it for an update and the first to check eviction + // Notice that the list of blocks is ordered from last to first. + let last_block = last_n_blocks.remove(0); + let first_block = last_n_blocks.last().unwrap().deref().clone(); + + // Init the cache with the 6 block before the last + let mut cache = TxIndex::new(&last_n_blocks, height); + + // Update the cache with the last block + let locator_tx_map = last_block + .txdata + .iter() + .map(|tx| (Locator::new(tx.txid()), tx.clone())) + .collect(); + + cache.update(last_block.deref().header, &locator_tx_map); + + // Check that the new data is in the cache + assert!(cache.blocks().contains(&last_block.block_hash())); + for (locator, _) in locator_tx_map.iter() { + assert!(cache.contains_key(locator)); + } + assert_eq!( + cache.tx_in_block[&last_block.block_hash()], + locator_tx_map.keys().cloned().collect::>() + ); + + // Check that the data from the first block has been evicted + assert!(!cache.blocks().contains(&first_block.block_hash())); + for tx in first_block.txdata.iter() { + assert!(!cache.contains_key(&Locator::new(tx.txid()))); + } + assert!(!cache.tx_in_block.contains_key(&first_block.block_hash())); + } + + #[tokio::test] + async fn test_remove_disconnected_block() { + let cache_size = 6; + let height = cache_size * 2; + let mut chain = Blockchain::default().with_height_and_txs(height, 42); + let mut cache: TxIndex = TxIndex::new( + &get_last_n_blocks(&mut chain, cache_size).await, + height as u32, + ); + + // TxIndex::fix removes the last connected block and removes all the associated data + for i in 0..cache_size { + let header = chain + .at_height(chain.get_block_count() as usize - i) + .deref() + .header; + let locators = cache.tx_in_block.get(&header.block_hash()).unwrap().clone(); + + // Make sure there's data regarding the target block in the cache before fixing it + assert_eq!(cache.blocks().len(), cache.size - i); + assert!(cache.blocks().contains(&header.block_hash())); + assert!(!locators.is_empty()); + for locator in locators.iter() { + assert!(cache.contains_key(locator)); + } + + cache.remove_disconnected_block(&header.block_hash()); + + // Check that the block data is not in the cache anymore + assert_eq!(cache.blocks().len(), cache.size - i - 1); + assert!(!cache.blocks().contains(&header.block_hash())); + assert!(cache.tx_in_block.get(&header.block_hash()).is_none()); + for locator in locators.iter() { + assert!(!cache.contains_key(locator)); + } + } + + // At this point the cache should be empty, fixing it further shouldn't do anything + for i in cache_size..cache_size * 2 { + assert!(cache.index.is_empty()); + assert!(cache.blocks().is_empty()); + assert!(cache.tx_in_block.is_empty()); + + let header = chain + .at_height(chain.get_block_count() as usize - i) + .deref() + .header; + cache.remove_disconnected_block(&header.block_hash()); + } + } +} diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index b1a1cbb..d08050d 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -4,12 +4,10 @@ use log; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; -use std::fmt; use std::iter::FromIterator; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; -use bitcoin::hash_types::BlockHash; use bitcoin::secp256k1::SecretKey; use bitcoin::{BlockHeader, Transaction}; use lightning::chain; @@ -24,133 +22,7 @@ use crate::dbm::DBM; use crate::extended_appointment::{AppointmentSummary, ExtendedAppointment, UUID}; use crate::gatekeeper::{Gatekeeper, MaxSlotsReached, UserInfo}; use crate::responder::{ConfirmationStatus, Responder, TransactionTracker}; - -/// Data structure used to cache locators computed from parsed blocks. -/// -/// Holds up to `size` blocks with their corresponding computed [Locator]s. -#[derive(Debug)] -struct LocatorCache { - /// A [Locator]:[Transaction] map. - cache: HashMap, - /// Vector of block hashes corresponding to the cached blocks. - blocks: Vec, - /// Map of [BlockHash]:[Vec]. Used to remove data from the cache. - tx_in_block: HashMap>, - /// Maximum size of the cache. - size: usize, -} - -impl fmt::Display for LocatorCache { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "cache: {:?}\n\nblocks: {:?}\n\ntx_in_block: {:?}\n\nsize: {}", - self.cache, self.blocks, self.tx_in_block, self.size - ) - } -} - -impl LocatorCache { - /// Creates a new [LocatorCache] instance. - /// The cache is initialized using the provided vector of blocks. - /// The size of the cache is defined as the size of `last_n_blocks`. - /// - /// # Panics - /// - /// Panics if any of the blocks in `last_n_blocks` is unchained. That is, if the given blocks - /// are not linked in strict descending order. - fn new(last_n_blocks: Vec) -> LocatorCache { - let size = last_n_blocks.len(); - let mut cache = LocatorCache { - cache: HashMap::new(), - blocks: Vec::with_capacity(size), - tx_in_block: HashMap::new(), - size, - }; - - for block in last_n_blocks.into_iter().rev() { - if let Some(prev_block_hash) = cache.blocks.last() { - if block.header.prev_blockhash != *prev_block_hash { - panic!("last_n_blocks contains unchained blocks"); - } - }; - - let locator_tx_map = block - .txdata - .iter() - .map(|tx| (Locator::new(tx.txid()), tx.clone())) - .collect(); - - cache.update(block.header, &locator_tx_map); - } - - cache - } - - /// Gets a transaction from the cache if present. [None] otherwise. - fn get_tx(&self, locator: Locator) -> Option<&Transaction> { - self.cache.get(&locator) - } - - /// Checks if the cache if full. - fn is_full(&self) -> bool { - self.blocks.len() > self.size - } - - /// Updates the cache by adding data from a new block. Removes the oldest block if the cache is full afterwards. - fn update( - &mut self, - block_header: BlockHeader, - locator_tx_map: &HashMap, - ) { - self.blocks.push(block_header.block_hash()); - - let locators = locator_tx_map - .iter() - .map(|(l, tx)| { - self.cache.insert(*l, tx.clone()); - *l - }) - .collect(); - - self.tx_in_block.insert(block_header.block_hash(), locators); - - if self.is_full() { - // Avoid logging during bootstrap - log::info!("New block added to cache: {}", block_header.block_hash()); - self.remove_oldest_block(); - } - } - - /// Fixes the [LocatorCache] removing disconnected data. - fn fix(&mut self, header: &BlockHeader) { - if let Some(locators) = self.tx_in_block.remove(&header.block_hash()) { - for locator in locators.iter() { - self.cache.remove(locator); - } - - // Blocks should be disconnected from last backwards. Log if that's not the case so we can revisit this and fix it. - if let Some(h) = self.blocks.pop() { - if h != header.block_hash() { - log::error!("Disconnected block does not match the oldest block stored in the LocatorCache ({} != {})", header.block_hash(), h); - } - } - } else { - log::warn!("The cache is already empty"); - } - } - - /// Removes the oldest block from the cache. - /// This removes data from `self.blocks`, `self.tx_in_block` and `self.cache`. - fn remove_oldest_block(&mut self) { - let oldest_hash = self.blocks.remove(0); - for locator in self.tx_in_block.remove(&oldest_hash).unwrap() { - self.cache.remove(&locator); - } - - log::info!("Oldest block removed from cache: {}", oldest_hash); - } -} +use crate::tx_index::TxIndex; /// Structure holding data regarding a breach. /// @@ -241,7 +113,7 @@ pub struct Watcher { /// A map between [Locator]s (user identifiers for [Appointment]s) and [UUID]s (tower identifiers). locator_uuid_map: Mutex>>, /// A cache of the [Locator]s computed for the transactions in the last few blocks. - locator_cache: Mutex, + locator_cache: Mutex>, /// A [Responder] instance. Data will be passed to it once triggered (if valid). responder: Arc, /// A [Gatekeeper] instance. Data regarding users is requested to it. @@ -261,7 +133,7 @@ impl Watcher { pub fn new( gatekeeper: Arc, responder: Arc, - last_n_blocks: Vec, + last_n_blocks: &[ValidatedBlock], last_known_block_height: u32, signing_key: SecretKey, tower_id: TowerId, @@ -282,7 +154,7 @@ impl Watcher { Watcher { appointments: Mutex::new(appointments), locator_uuid_map: Mutex::new(locator_uuid_map), - locator_cache: Mutex::new(LocatorCache::new(last_n_blocks)), + locator_cache: Mutex::new(TxIndex::new(last_n_blocks, last_known_block_height)), responder, gatekeeper, last_known_block_height: AtomicU32::new(last_known_block_height), @@ -362,7 +234,7 @@ impl Watcher { .locator_cache .lock() .unwrap() - .get_tx(extended_appointment.locator()) + .get(&extended_appointment.locator()) { // Appointments that were triggered in blocks held in the cache Some(dispute_tx) => { @@ -879,7 +751,10 @@ impl chain::Listen for Watcher { /// Fixes the [LocatorCache] by removing the disconnected data and updates the last_known_block_height. fn block_disconnected(&self, header: &BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); - self.locator_cache.lock().unwrap().fix(header); + self.locator_cache + .lock() + .unwrap() + .remove_disconnected_block(&header.block_hash()); self.last_known_block_height .store(height - 1, Ordering::Release); } @@ -982,122 +857,6 @@ mod tests { assert_eq!(TowerId(recovered_pk), tower_id); } - #[tokio::test] - async fn test_cache_new() { - let mut chain = Blockchain::default().with_height(10); - let last_six_blocks = get_last_n_blocks(&mut chain, 6).await; - let blocks: Vec = last_six_blocks - .iter() - .map(|block| block.deref().clone()) - .collect(); - - let cache = LocatorCache::new(last_six_blocks); - assert_eq!(blocks.len(), cache.size); - for block in blocks.iter() { - assert!(cache.blocks.contains(&block.block_hash())); - - let mut locators = Vec::new(); - for tx in block.txdata.iter() { - let locator = Locator::new(tx.txid()); - assert!(cache.cache.contains_key(&locator)); - locators.push(locator); - } - - assert_eq!(cache.tx_in_block[&block.block_hash()], locators); - } - } - - #[tokio::test] - async fn test_cache_update() { - let mut chain = Blockchain::default().with_height(10); - let mut last_n_blocks = get_last_n_blocks(&mut chain, 7).await; - - // Safe the last block to use it for an update and the first to check eviction - // Notice that the list of blocks is ordered from last to first. - let last_block = last_n_blocks.remove(0); - let first_block = last_n_blocks.last().unwrap().deref().clone(); - - // Init the cache with the 6 block before the last - let mut cache = LocatorCache::new(last_n_blocks); - - // Update the cache with the last block - let locator_tx_map = last_block - .txdata - .iter() - .map(|tx| (Locator::new(tx.txid()), tx.clone())) - .collect(); - - cache.update(last_block.deref().header, &locator_tx_map); - - // Check that the new data is in the cache - assert!(cache.blocks.contains(&last_block.block_hash())); - for (locator, _) in locator_tx_map.iter() { - assert!(cache.cache.contains_key(locator)); - } - assert_eq!( - cache.tx_in_block[&last_block.block_hash()], - locator_tx_map.keys().cloned().collect::>() - ); - - // Check that the data from the first block has been evicted - assert!(!cache.blocks.contains(&first_block.block_hash())); - for tx in first_block.txdata.iter() { - assert!(!cache.cache.contains_key(&Locator::new(tx.txid()))); - } - assert!(!cache.tx_in_block.contains_key(&first_block.block_hash())); - } - - #[tokio::test] - async fn test_cache_fix() { - let cache_size = 6; - let mut chain = Blockchain::default().with_height_and_txs(cache_size * 2, 42); - - let last_n_blocks = get_last_n_blocks(&mut chain, cache_size).await; - - // Init the cache with the 6 block before the last - let mut cache = LocatorCache::new(last_n_blocks); - - // LocatorCache::fix removes the last connected block and removes all the associated data - for i in 0..cache_size { - let header = chain - .at_height(chain.get_block_count() as usize - i) - .deref() - .header; - let locators = cache.tx_in_block.get(&header.block_hash()).unwrap().clone(); - - // Make sure there's data regarding the target block in the cache before fixing it - assert_eq!(cache.blocks.len(), cache.size - i); - assert!(cache.blocks.contains(&header.block_hash())); - assert!(!locators.is_empty()); - for locator in locators.iter() { - assert!(cache.cache.contains_key(locator)); - } - - cache.fix(&header); - - // Check that the block data is not in the cache anymore - assert_eq!(cache.blocks.len(), cache.size - i - 1); - assert!(!cache.blocks.contains(&header.block_hash())); - assert!(cache.tx_in_block.get(&header.block_hash()).is_none()); - for locator in locators.iter() { - assert!(!cache.cache.contains_key(locator)); - } - } - - // At this point the cache should be empty, fixing it further shouldn't do anything - for i in cache_size..cache_size * 2 { - assert!(cache.cache.is_empty()); - assert!(cache.blocks.is_empty()); - assert!(cache.tx_in_block.is_empty()); - - let header = chain - .at_height(chain.get_block_count() as usize - i) - .deref() - .header; - cache.fix(&header); - } - } - #[tokio::test] async fn test_new() { // A fresh watcher has no associated data @@ -2122,7 +1881,7 @@ mod tests { .locator_cache .lock() .unwrap() - .blocks + .blocks() .contains(&last_block_header.block_hash())); watcher.block_disconnected(&last_block_header, start_height); @@ -2135,7 +1894,7 @@ mod tests { .locator_cache .lock() .unwrap() - .blocks + .blocks() .contains(&last_block_header.block_hash())); } } From 1788b4d7226e6f9d00dab987cac707bd0596aaf5 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 6 Oct 2022 13:37:40 +0200 Subject: [PATCH 039/119] Adds TxIndex to Responder --- teos/src/carrier.rs | 260 ++++++++++++++++++------------------- teos/src/main.rs | 31 ++++- teos/src/responder.rs | 284 +++++++++++++++++++++++++++++------------ teos/src/test_utils.rs | 129 +++++++------------ teos/src/watcher.rs | 14 +- 5 files changed, 408 insertions(+), 310 deletions(-) diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 5ead0d8..6b576e1 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::responder::ConfirmationStatus; use crate::{errors, rpc_errors}; -use bitcoin::{BlockHash, Transaction, Txid}; +use bitcoin::{Transaction, Txid}; use bitcoincore_rpc::{ jsonrpc::error::Error::Rpc as RpcError, jsonrpc::error::Error::Transport as TransportError, Client as BitcoindClient, Error::JsonRpc as JsonRpcError, RpcApi, @@ -22,7 +22,7 @@ pub struct Carrier { /// A map of receipts already issued by the [Carrier]. /// Used to prevent potentially re-sending the same transaction over and over. issued_receipts: HashMap, - /// The last known block header. + /// The last known block height. block_height: u32, } @@ -41,6 +41,11 @@ impl Carrier { } } + /// The last known block height. + pub(crate) fn block_height(&self) -> u32 { + self.block_height + } + /// Clears the receipts cached by the [Carrier]. Should be called periodically to prevent it from /// growing unbounded. pub(crate) fn clear_receipts(&mut self) { @@ -100,11 +105,14 @@ impl Carrier { } rpc_errors::RPC_VERIFY_ALREADY_IN_CHAIN => { log::info!( - "Transaction is already in the blockchain: {}. Getting confirmation count", + "Transaction was confirmed long ago, not keeping track of it: {}", tx.txid() ); - ConfirmationStatus::ConfirmedIn(self.get_tx_height(&tx.txid()).unwrap()) + // Given we are not using txindex, if a transaction bounces we cannot get its confirmation count. However, [send_transaction] is guarded by + // checking whether the transaction id can be found in the [Responder]'s [TxIndex], meaning that if the transaction bounces it was confirmed long + // ago (> IRREVOCABLY_RESOLVED), so we don't need to worry about it. + ConfirmationStatus::IrrevocablyResolved } rpc_errors::RPC_DESERIALIZATION_ERROR => { // Adding this here just for completeness. We should never end up here. The Carrier only sends txs handed by the Responder, @@ -139,77 +147,44 @@ impl Carrier { receipt } - /// Gets the block height at where a given [Transaction] was confirmed at (if any). - fn get_tx_height(&self, txid: &Txid) -> Option { - if let Some(block_hash) = self.get_block_hash_for_tx(txid) { - self.get_block_height(&block_hash) - } else { - None - } - } - - /// Queries the height of a given [Block](bitcoin::Block). Returns it if the block can be found. Returns [None] otherwise. - fn get_block_height(&self, block_hash: &BlockHash) -> Option { - self.hang_until_bitcoind_reachable(); - - match self.bitcoin_cli.get_block_header_info(block_hash) { - Ok(header_data) => Some(header_data.height as u32), - Err(JsonRpcError(RpcError(rpcerr))) => match rpcerr.code { - rpc_errors::RPC_INVALID_ADDRESS_OR_KEY => { - log::info!("Block not found: {}", block_hash); - None - } - e => { - log::error!("Unexpected error code when calling getblockheader: {}", e); - None - } - }, - Err(JsonRpcError(TransportError(_))) => { - // Connection refused, bitcoind is down. - log::error!("Connection lost with bitcoind, retrying request when possible"); - self.flag_bitcoind_unreachable(); - self.get_block_height(block_hash) - } - // TODO: This may need finer catching. - Err(e) => { - log::error!("Unexpected JSONRPCError when calling getblockheader: {}", e); - None - } - } - } - - /// Gets the block hash where a given [Transaction] was confirmed at (if any). - pub(crate) fn get_block_hash_for_tx(&self, txid: &Txid) -> Option { + /// Checks whether a given transaction can be found in the mempool. + /// + /// This uses `getrawtransaction` under the hood and, therefore, its behavior depends on whether `txindex` is enabled in bitcoind. + /// If `txindex` is disabled (default), it will only pull data from the mempool. Otherwise, it will also pull data from the transaction + /// index. Hence, we need to check whether the returned struct has any of the block related datum set (such as `blockhash`). + pub(crate) fn in_mempool(&self, txid: &Txid) -> bool { self.hang_until_bitcoind_reachable(); match self.bitcoin_cli.get_raw_transaction_info(txid, None) { - Ok(tx_data) => tx_data.blockhash, + Ok(tx) => tx.blockhash.is_none(), Err(JsonRpcError(RpcError(rpcerr))) => match rpcerr.code { rpc_errors::RPC_INVALID_ADDRESS_OR_KEY => { - log::info!("Transaction not found in mempool nor blockchain: {}", txid); - None + log::info!("Transaction not found in mempool: {}", txid); + false } e => { + // DISCUSS: This could result in a silent error with unknown consequences log::error!( "Unexpected error code when calling getrawtransaction: {}", e ); - None + false } }, Err(JsonRpcError(TransportError(_))) => { // Connection refused, bitcoind is down. log::error!("Connection lost with bitcoind, retrying request when possible"); self.flag_bitcoind_unreachable(); - self.get_block_hash_for_tx(txid) + self.in_mempool(txid) } // TODO: This may need finer catching. Err(e) => { + // DISCUSS: This could result in a silent error with unknown consequences log::error!( "Unexpected JSONRPCError when calling getrawtransaction: {}", e ); - None + false } } } @@ -221,7 +196,7 @@ mod tests { use std::thread; use crate::test_utils::{get_random_tx, start_server, BitcoindMock, MockOptions, START_HEIGHT}; - use teos_common::test_utils::TX_HEX; + use teos_common::test_utils::{TXID_HEX, TX_HEX}; use bitcoin::consensus; use bitcoin::hashes::hex::FromHex; @@ -241,11 +216,10 @@ mod tests { #[test] fn test_clear_receipts() { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); @@ -265,7 +239,25 @@ mod tests { #[test] fn test_send_transaction_ok() { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); + let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); + let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); + let start_height = START_HEIGHT as u32; + start_server(bitcoind_mock.server); + + let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); + let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); + let r = carrier.send_transaction(&tx); + + assert_eq!(r, ConfirmationStatus::InMempoolSince(start_height)); + + // Check the receipt is on the cache + assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + } + + #[test] + fn test_send_transaction_ok_already_in_mempool() { + let bitcoind_mock = BitcoindMock::new(MockOptions::in_mempool()); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; @@ -328,20 +320,19 @@ mod tests { #[test] fn test_send_transaction_verify_already_in_chain() { - let bitcoind_mock = BitcoindMock::new(MockOptions::new( + let bitcoind_mock = BitcoindMock::new(MockOptions::with_error( rpc_errors::RPC_VERIFY_ALREADY_IN_CHAIN as i64, - BlockHash::default(), - START_HEIGHT, )); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; start_server(bitcoind_mock.server); + let mut carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); let tx = consensus::deserialize(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); let r = carrier.send_transaction(&tx); - assert_eq!(r, ConfirmationStatus::ConfirmedIn(start_height)); + assert_eq!(r, ConfirmationStatus::IrrevocablyResolved); // Check the receipt is on the cache assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); @@ -372,7 +363,7 @@ mod tests { #[test] fn test_send_transaction_connection_error() { // Try to connect to an offline bitcoind. - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); let bitcoind_reachable = Arc::new((Mutex::new(false), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; @@ -399,91 +390,86 @@ mod tests { } #[test] - fn test_get_tx_height_ok() { - let target_height = 21; + fn test_in_mempool() { + let bitcoind_mock = BitcoindMock::new(MockOptions::in_mempool()); + let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); + let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); + let start_height = START_HEIGHT as u32; + start_server(bitcoind_mock.server); + + let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); + let txid = Txid::from_hex(TXID_HEX).unwrap(); + assert!(carrier.in_mempool(&txid)); + } + + #[test] + fn test_not_in_mempool() { + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); + let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); + let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); + let start_height = START_HEIGHT as u32; + start_server(bitcoind_mock.server); + + let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); + let txid = Txid::from_hex(TXID_HEX).unwrap(); + assert!(!carrier.in_mempool(&txid)); + } + + #[test] + fn test_not_in_mempool_via_error() { + let bitcoind_mock = BitcoindMock::new(MockOptions::with_error( + rpc_errors::RPC_INVALID_ADDRESS_OR_KEY as i64, + )); + let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); + let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); + let start_height = START_HEIGHT as u32; + start_server(bitcoind_mock.server); + + let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); + let txid = Txid::from_hex(TXID_HEX).unwrap(); + assert!(!carrier.in_mempool(&txid)); + } + + #[test] + fn test_in_mempool_unexpected_error() { let bitcoind_mock = - BitcoindMock::new(MockOptions::with_block(BlockHash::default(), target_height)); + BitcoindMock::new(MockOptions::with_error(rpc_errors::RPC_MISC_ERROR as i64)); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); let start_height = START_HEIGHT as u32; start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); + let txid = Txid::from_hex(TXID_HEX).unwrap(); + assert!(!carrier.in_mempool(&txid)); + } + + #[test] + fn test_in_mempool_connection_error() { + // Try to connect to an offline bitcoind. + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); + let bitcoind_reachable = Arc::new((Mutex::new(false), Condvar::new())); + let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); + let start_height = START_HEIGHT as u32; + let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable.clone(), start_height); + + let txid = Txid::from_hex(TXID_HEX).unwrap(); + let delay = std::time::Duration::new(3, 0); + + thread::spawn(move || { + thread::sleep(delay); + let (reachable, notifier) = &*bitcoind_reachable; + *reachable.lock().unwrap() = true; + notifier.notify_all(); + }); + + let before = std::time::Instant::now(); + carrier.in_mempool(&txid); + + // Check the request has hanged for ~delay assert_eq!( - carrier.get_tx_height(&tx.txid()), - Some(target_height as u32) + (std::time::Instant::now() - before).as_secs(), + delay.as_secs() ); } - - #[test] - fn test_get_tx_height_not_found() { - // Hee we are not testing the case where the block hash is unknown (which will also return None). This is because we only - // learn block hashes from bitcoind, and once a block is known, it cannot disappear (ir can be disconnected, but not banish). - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); - let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); - let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); - - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); - assert_eq!(carrier.get_tx_height(&tx.txid()), None); - } - - #[test] - fn test_get_block_height_ok() { - let target_height = 21; - let block_hash = BlockHash::default(); - let bitcoind_mock = BitcoindMock::new(MockOptions::with_block(block_hash, target_height)); - let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); - let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); - - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - assert_eq!( - carrier.get_block_height(&block_hash), - Some(target_height as u32) - ); - } - - #[test] - fn test_get_block_height_not_found() { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); - let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); - let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); - - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - assert_eq!(carrier.get_block_height(&BlockHash::default()), None); - } - - #[test] - fn test_get_block_hash_for_tx_ok() { - let block_hash = BlockHash::default(); - let bitcoind_mock = BitcoindMock::new(MockOptions::with_block(block_hash, 21)); - let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); - let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); - - let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - assert_eq!(carrier.get_block_hash_for_tx(&tx.txid()), Some(block_hash)); - } - - #[test] - fn test_get_block_hash_for_tx_not_found() { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); - let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); - let start_height = START_HEIGHT as u32; - start_server(bitcoind_mock.server); - - let tx = consensus::deserialize::(&Vec::from_hex(TX_HEX).unwrap()).unwrap(); - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - assert_eq!(carrier.get_block_hash_for_tx(&tx.txid()), None); - } } diff --git a/teos/src/main.rs b/teos/src/main.rs index de9f412..cb0bfca 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -32,6 +32,7 @@ use teos::responder::Responder; use teos::tls::tls_init; use teos::watcher::Watcher; +use teos_common::constants::IRREVOCABLY_RESOLVED; use teos_common::cryptography::get_random_keypair; use teos_common::TowerId; @@ -44,7 +45,7 @@ where B: DerefMut + Sized + Send + Sync, T: BlockSource, { - let mut last_n_blocks = Vec::new(); + let mut last_n_blocks = Vec::with_capacity(n); for _ in 0..n { let block = poller.fetch_block(&last_known_block).await.unwrap(); last_known_block = poller @@ -187,6 +188,20 @@ async fn main() { } else { validate_best_block_header(&mut derefed).await.unwrap() }; + + // DISCUSS: This is not really required (and only triggered in regtest). This is only in place so the caches can be + // populated with enough blocks mainly because the size of the cache is based on the amount of blocks passed when initializing. + // However, we could add an additional parameter to specify the size of the cache, and initialize with however may blocks we + // could pull from the backend. Adding this functionality just for regtest seemed unnecessary though, hence the check. + if tip.height < IRREVOCABLY_RESOLVED { + log::error!( + "Not enough blocks to start teosd (required: {}). Mine at least {} more", + IRREVOCABLY_RESOLVED, + IRREVOCABLY_RESOLVED - tip.height + ); + std::process::exit(1); + } + log::info!("Last known block: {}", tip.header.block_hash()); // This is how chain poller names bitcoin networks. @@ -197,7 +212,7 @@ async fn main() { }; let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); - let last_n_blocks = get_last_n_blocks(&mut poller, tip, 6).await; + let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize).await; // Build components let gatekeeper = Arc::new(Gatekeeper::new( @@ -208,12 +223,18 @@ async fn main() { dbm.clone(), )); - let carrier = Carrier::new(rpc, bitcoind_reachable.clone(), tip.deref().height); - let responder = Arc::new(Responder::new(carrier, gatekeeper.clone(), dbm.clone())); + let carrier = Carrier::new(rpc, bitcoind_reachable.clone(), tip.height); + let responder = Arc::new(Responder::new( + &last_n_blocks, + tip.height, + carrier, + gatekeeper.clone(), + dbm.clone(), + )); let watcher = Arc::new(Watcher::new( gatekeeper.clone(), responder.clone(), - &last_n_blocks, + &last_n_blocks[0..6], tip.height, tower_sk, TowerId(tower_pk), diff --git a/teos/src/responder.rs b/teos/src/responder.rs index f2ba7a0..6616949 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -4,9 +4,10 @@ use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use std::sync::{Arc, Mutex}; -use bitcoin::consensus; +use bitcoin::{consensus, BlockHash}; use bitcoin::{BlockHeader, Transaction, Txid}; use lightning::chain; +use lightning_block_sync::poll::ValidatedBlock; use teos_common::constants; use teos_common::protos as common_msgs; @@ -16,6 +17,7 @@ use crate::carrier::Carrier; use crate::dbm::DBM; use crate::extended_appointment::UUID; use crate::gatekeeper::{Gatekeeper, UserInfo}; +use crate::tx_index::TxIndex; use crate::watcher::Breach; /// Number of missed confirmations to wait before rebroadcasting a transaction. @@ -26,6 +28,7 @@ const CONFIRMATIONS_BEFORE_RETRY: u8 = 6; pub enum ConfirmationStatus { ConfirmedIn(u32), InMempoolSince(u32), + IrrevocablyResolved, Rejected(i32), ReorgedOut, } @@ -59,6 +62,14 @@ impl ConfirmationStatus { None } } + + /// Whether the transaction was accepted by the underlying node. + pub fn accepted(&self) -> bool { + matches!( + self, + ConfirmationStatus::ConfirmedIn(_) | &ConfirmationStatus::InMempoolSince(_) + ) + } } /// Minimal data required in memory to keep track of transaction trackers. @@ -130,6 +141,8 @@ pub struct Responder { trackers: Mutex>, /// A map between [Txid]s and [UUID]s. tx_tracker_map: Mutex>>, + /// A local, pruned, [TxIndex] used to avoid the need of `txindex=1`. + tx_index: Mutex>, /// A [Carrier] instance. Data is sent to the `bitcoind` through it. carrier: Mutex, /// A [Gatekeeper] instance. Data regarding users is requested to it. @@ -140,7 +153,13 @@ pub struct Responder { impl Responder { /// Creates a new [Responder] instance. - pub fn new(carrier: Carrier, gatekeeper: Arc, dbm: Arc>) -> Self { + pub fn new( + last_n_blocs: &[ValidatedBlock], + last_known_block_height: u32, + carrier: Carrier, + gatekeeper: Arc, + dbm: Arc>, + ) -> Self { let mut trackers = HashMap::new(); let mut tx_tracker_map: HashMap> = HashMap::new(); @@ -158,6 +177,7 @@ impl Responder { carrier: Mutex::new(carrier), trackers: Mutex::new(trackers), tx_tracker_map: Mutex::new(tx_tracker_map), + tx_index: Mutex::new(TxIndex::new(last_n_blocs, last_known_block_height)), dbm, gatekeeper, } @@ -189,12 +209,20 @@ impl Responder { return tracker.status; } - let status = self - .carrier - .lock() - .unwrap() - .send_transaction(&breach.penalty_tx); - if !matches!(status, ConfirmationStatus::Rejected { .. }) { + let mut carrier = self.carrier.lock().unwrap(); + let tx_index = self.tx_index.lock().unwrap(); + + // Check whether the transaction is in mempool or part of our internal txindex. Send it to our node otherwise. + let status = if carrier.in_mempool(&breach.penalty_tx.txid()) { + // If it's in mempool we assume it was just included + ConfirmationStatus::InMempoolSince(carrier.block_height()) + } else if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { + ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) + } else { + carrier.send_transaction(&breach.penalty_tx) + }; + + if status.accepted() { self.add_tracker(uuid, breach, user_id, status); } @@ -377,12 +405,15 @@ impl Responder { let mut trackers = self.trackers.lock().unwrap(); let mut carrier = self.carrier.lock().unwrap(); + let tx_index = self.tx_index.lock().unwrap(); for (uuid, (penalty_tx, dispute_tx)) in txs.into_iter() { let status = if let Some(dispute_tx) = dispute_tx { - // The tracker was reorged out, and the dispute may potentially not be in the chain anymore. - if carrier.get_block_hash_for_tx(&dispute_tx.txid()).is_some() { - // Dispute tx is on chain, so we only need to care about the penalty + // The tracker was reorged out, and the dispute may potentially not be in the chain (or mempool) anymore. + if tx_index.contains_key(&dispute_tx.txid()) + | carrier.in_mempool(&dispute_tx.txid()) + { + // Dispute tx is on chain (or mempool), so we only need to care about the penalty carrier.send_transaction(&penalty_tx) } else { // Dispute tx has also been reorged out, meaning that both transactions need to be broadcast. @@ -503,7 +534,13 @@ impl chain::Listen for Responder { log::info!("New block received: {}", header.block_hash()); self.carrier.lock().unwrap().update_height(height); - if self.trackers.lock().unwrap().len() > 0 { + let txs = txdata + .iter() + .map(|(_, tx)| (tx.txid(), header.block_hash())) + .collect(); + self.tx_index.lock().unwrap().update(*header, &txs); + + if !self.trackers.lock().unwrap().is_empty() { // Complete those appointments that are due at this height let completed_trackers = self.check_confirmations( &txdata.iter().map(|(_, tx)| tx.txid()).collect::>(), @@ -555,6 +592,10 @@ impl chain::Listen for Responder { fn block_disconnected(&self, header: &BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); self.carrier.lock().unwrap().update_height(height); + self.tx_index + .lock() + .unwrap() + .remove_disconnected_block(&header.block_hash()); for tracker in self.trackers.lock().unwrap().values_mut() { // The transaction has been unconfirmed. Flag it as reorged out so we can rebroadcast it. @@ -570,19 +611,19 @@ mod tests { use super::*; use lightning::chain::Listen; - use std::ops::Deref; use std::sync::{Arc, Mutex}; use crate::dbm::DBM; use crate::gatekeeper::UserInfo; use crate::rpc_errors; use crate::test_utils::{ - create_carrier, generate_dummy_appointment_with_user, generate_uuid, get_random_breach, - get_random_tracker, get_random_tx, store_appointment_and_fks_to_db, BitcoindStopper, - Blockchain, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, - START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + create_carrier, generate_dummy_appointment_with_user, generate_uuid, get_last_n_blocks, + get_random_breach, get_random_tracker, get_random_tx, store_appointment_and_fks_to_db, + BitcoindStopper, Blockchain, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, + SLOTS, START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, }; + use teos_common::constants::IRREVOCABLY_RESOLVED; use teos_common::dbm::Error as DBError; use teos_common::test_utils::get_random_user_id; @@ -640,20 +681,30 @@ mod tests { } } - fn create_responder( - chain: &Blockchain, + async fn create_responder( + chain: &mut Blockchain, gatekeeper: Arc, dbm: Arc>, query: MockedServerQuery, ) -> (Responder, BitcoindStopper) { - let tip = chain.tip(); - let (carrier, bitcoind_stopper) = create_carrier(query, tip.deref().height); - (Responder::new(carrier, gatekeeper, dbm), bitcoind_stopper) + let height = if chain.tip().height < IRREVOCABLY_RESOLVED { + chain.tip().height + } else { + IRREVOCABLY_RESOLVED + }; + + let last_n_blocks = get_last_n_blocks(chain, height as usize).await; + + let (carrier, bitcoind_stopper) = create_carrier(query, chain.tip().height); + ( + Responder::new(&last_n_blocks, chain.tip().height, carrier, gatekeeper, dbm), + bitcoind_stopper, + ) } - fn init_responder_with_chain_and_dbm( + async fn init_responder_with_chain_and_dbm( mocked_query: MockedServerQuery, - chain: &Blockchain, + chain: &mut Blockchain, dbm: Arc>, ) -> (Responder, BitcoindStopper) { let gk = Gatekeeper::new( @@ -663,13 +714,13 @@ mod tests { EXPIRY_DELTA, dbm.clone(), ); - create_responder(chain, Arc::new(gk), dbm, mocked_query) + create_responder(chain, Arc::new(gk), dbm, mocked_query).await } - fn init_responder(mocked_query: MockedServerQuery) -> (Responder, BitcoindStopper) { + async fn init_responder(mocked_query: MockedServerQuery) -> (Responder, BitcoindStopper) { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - let chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); - init_responder_with_chain_and_dbm(mocked_query, &chain, dbm) + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); + init_responder_with_chain_and_dbm(mocked_query, &mut chain, dbm).await } #[test] @@ -712,13 +763,14 @@ mod tests { assert_eq!(ConfirmationStatus::ReorgedOut.to_db_data(), None); } - #[test] - fn test_responder_new() { + #[tokio::test] + async fn test_responder_new() { // A fresh responder has no associated data - let chain = Blockchain::default().with_height(START_HEIGHT); + let mut chain = Blockchain::default().with_height(START_HEIGHT); let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (responder, _s) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm.clone()); + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm.clone()) + .await; assert!(responder.is_fresh()); // If we add some trackers to the system and create a new Responder reusing the same db @@ -740,15 +792,15 @@ mod tests { // Create a new Responder reusing the same DB and check that the data is loaded let (another_r, _) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; assert!(!responder.is_fresh()); assert_eq!(responder, another_r); } - #[test] - fn test_handle_breach_delivered() { + #[tokio::test] + async fn test_handle_breach_accepted() { let start_height = START_HEIGHT as u32; - let (responder, _s) = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let user_id = get_random_user_id(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); @@ -792,11 +844,82 @@ mod tests { .contains_key(&another_breach.penalty_tx.txid())); } - #[test] - fn test_handle_breach_rejected() { + #[tokio::test] + async fn test_handle_breach_accepted_in_mempool() { + let start_height = START_HEIGHT as u32; + let (responder, _s) = init_responder(MockedServerQuery::InMempoool).await; + + let user_id = get_random_user_id(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); + + let breach = get_random_breach(); + let penalty_txid = breach.penalty_tx.txid(); + + assert_eq!( + responder.handle_breach(uuid, breach, user_id), + ConfirmationStatus::InMempoolSince(start_height) + ); + assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + assert_eq!( + responder.trackers.lock().unwrap()[&uuid].status, + ConfirmationStatus::InMempoolSince(start_height) + ); + assert!(responder + .tx_tracker_map + .lock() + .unwrap() + .contains_key(&penalty_txid)); + } + + #[tokio::test] + async fn test_handle_breach_accepted_in_txindex() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; + + let user_id = get_random_user_id(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); + + let breach = get_random_breach(); + let penalty_txid = breach.penalty_tx.txid(); + + // Add the tx to our txindex + let target_block_hash = *responder.tx_index.lock().unwrap().blocks().get(2).unwrap(); + responder + .tx_index + .lock() + .unwrap() + .index_mut() + .insert(penalty_txid, target_block_hash); + let target_height = responder + .tx_index + .lock() + .unwrap() + .get_height(&target_block_hash) + .unwrap() as u32; + + assert_eq!( + responder.handle_breach(uuid, breach, user_id), + ConfirmationStatus::ConfirmedIn(target_height) + ); + assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + assert_eq!( + responder.trackers.lock().unwrap()[&uuid].status, + ConfirmationStatus::ConfirmedIn(target_height) + ); + assert!(responder + .tx_tracker_map + .lock() + .unwrap() + .contains_key(&penalty_txid)); + } + + #[tokio::test] + async fn test_handle_breach_rejected() { let (responder, _s) = init_responder(MockedServerQuery::Error( rpc_errors::RPC_VERIFY_ERROR as i64, - )); + )) + .await; let user_id = get_random_user_id(); let uuid = generate_uuid(); @@ -815,9 +938,9 @@ mod tests { .contains_key(&penalty_txid)); } - #[test] - fn test_add_tracker() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_add_tracker() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let start_height = START_HEIGHT as u32; // Add the necessary FKs in the database @@ -937,12 +1060,12 @@ mod tests { ); } - #[test] - fn test_has_tracker() { + #[tokio::test] + async fn test_has_tracker() { // Has tracker should return true as long as the given tracker is held by the Responder. // As long as the tracker is in Responder.trackers and Responder.tx_tracker_map, the return // must be true. - let (responder, _s) = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Add a new tracker let user_id = get_random_user_id(); @@ -968,11 +1091,11 @@ mod tests { assert!(!responder.has_tracker(uuid)); } - #[test] - fn test_get_tracker() { + #[tokio::test] + async fn test_get_tracker() { // Should return a tracker as long as it exists let start_height = START_HEIGHT as u32; - let (responder, _s) = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Store the user and the appointment in the database so we can add the tracker later on (due to FK restrictions) let user_id = get_random_user_id(); @@ -1008,9 +1131,9 @@ mod tests { assert_eq!(responder.get_tracker(uuid), None); } - #[test] - fn test_check_confirmations() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_check_confirmations() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let target_height = (START_HEIGHT * 2) as u32; // Unconfirmed transactions that miss a confirmation will be added to missed_confirmations (if not there) or their missed confirmation count till be increased @@ -1114,9 +1237,9 @@ mod tests { } } - #[test] - fn test_get_txs_to_rebroadcast() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_get_txs_to_rebroadcast() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let current_height = 100; let user_id = get_random_user_id(); @@ -1161,10 +1284,10 @@ mod tests { assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); } - #[test] - fn test_get_txs_to_rebroadcast_reorged() { + #[tokio::test] + async fn test_get_txs_to_rebroadcast_reorged() { // For reorged transactions this works a bit different, the dispute transaction will also be returned here - let (responder, _s) = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let current_height = 100; let user_id = get_random_user_id(); @@ -1202,7 +1325,7 @@ mod tests { // Since we are adding trackers using add_trackers we'll need to manually change the state of the transaction // (reorged transactions are not passed to add_tracker, they are detected after they are already there). - // Not doing should will trigger an error in the dbm since reorged transactions are not stored in the db. + // Not doing so will trigger an error in the dbm since reorged transactions are not stored in the db. if i % 2 == 0 { responder .trackers @@ -1223,9 +1346,9 @@ mod tests { assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); } - #[test] - fn test_get_outdated_trackers() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_get_outdated_trackers() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Outdated trackers are those whose associated subscription is outdated and have not been confirmed yet (they don't have // a single confirmation). @@ -1271,11 +1394,11 @@ mod tests { ); } - #[test] - fn test_rebroadcast_accepted() { + #[tokio::test] + async fn test_rebroadcast_accepted() { // This test positive rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a // dedicated test (against bitcoind, not mocked). - let (responder, _s) = init_responder(MockedServerQuery::Regular); + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let current_height = 100; // Add user to the database @@ -1339,13 +1462,14 @@ mod tests { assert!(rejected.is_empty()); } - #[test] - fn test_rebroadcast_rejected() { + #[tokio::test] + async fn test_rebroadcast_rejected() { // This test negative rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a // dedicated test (against bitcoind, not mocked). let (responder, _s) = init_responder(MockedServerQuery::Error( rpc_errors::RPC_VERIFY_ERROR as i64, - )); + )) + .await; let current_height = 100; // Add user to the database @@ -1408,9 +1532,9 @@ mod tests { assert!(accepted.is_empty()); } - #[test] - fn test_delete_trackers_from_memory() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_delete_trackers_from_memory() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Add user to the database let user_id = get_random_user_id(); @@ -1465,9 +1589,9 @@ mod tests { } } - #[test] - fn test_delete_trackers() { - let (responder, _s) = init_responder(MockedServerQuery::Regular); + #[tokio::test] + async fn test_delete_trackers() { + let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Add user to the database let user_id = get_random_user_id(); @@ -1601,13 +1725,13 @@ mod tests { } } - #[test] - fn test_filtered_block_connected() { + #[tokio::test] + async fn test_filtered_block_connected() { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let start_height = START_HEIGHT * 2; let mut chain = Blockchain::default().with_height(start_height); let (responder, _s) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; // block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder // is keeping track of. @@ -1846,12 +1970,12 @@ mod tests { ); } - #[test] - fn test_block_disconnected() { + #[tokio::test] + async fn test_block_disconnected() { let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - let chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); let (responder, _s) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &chain, dbm); + init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; // Add user to the database let user_id = get_random_user_id(); diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 8d78e4d..f8cdf7a 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -8,7 +8,6 @@ */ use rand::Rng; -use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex}; use std::thread; @@ -36,6 +35,7 @@ use lightning_block_sync::{ AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache, }; +use teos_common::constants::IRREVOCABLY_RESOLVED; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::test_utils::{generate_random_appointment, get_random_user_id, TXID_HEX, TX_HEX}; use teos_common::UserId; @@ -46,6 +46,7 @@ use crate::dbm::DBM; use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::{Gatekeeper, UserInfo}; use crate::responder::{ConfirmationStatus, Responder, TransactionTracker}; +use crate::rpc_errors; use crate::watcher::{Breach, Watcher}; pub(crate) const SLOTS: u32 = 21; @@ -353,18 +354,15 @@ pub(crate) fn store_appointment_and_fks_to_db( } pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec { - let tip = chain.tip(); - let poller = ChainPoller::new(chain, Network::Bitcoin); + let mut last_n_blocks = Vec::with_capacity(n); + let mut last_known_block = Ok(chain.tip()); + let poller = ChainPoller::new(chain, Network::Regtest); - let mut last_n_blocks = Vec::new(); - let mut last_known_block = tip; for _ in 0..n { - let block = poller.fetch_block(&last_known_block).await.unwrap(); - last_known_block = poller - .look_up_previous_header(&last_known_block) - .await - .unwrap(); + let header = last_known_block.unwrap(); + let block = poller.fetch_block(&header).await.unwrap(); last_n_blocks.push(block); + last_known_block = poller.look_up_previous_header(&header).await; } last_n_blocks @@ -372,12 +370,14 @@ pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec (Carrier, BitcoindStopper) { let bitcoind_mock = match query { - MockedServerQuery::Regular => BitcoindMock::new(MockOptions::empty()), + MockedServerQuery::Regular => BitcoindMock::new(MockOptions::default()), + MockedServerQuery::InMempoool => BitcoindMock::new(MockOptions::in_mempool()), MockedServerQuery::Error(x) => BitcoindMock::new(MockOptions::with_error(x)), }; let bitcoin_cli = Arc::new(BitcoindClient::new(bitcoind_mock.url(), Auth::None).unwrap()); @@ -390,17 +390,23 @@ pub(crate) fn create_carrier(query: MockedServerQuery, height: u32) -> (Carrier, ) } -pub(crate) fn create_responder( - tip: ValidatedBlockHeader, +pub(crate) async fn create_responder( + chain: &mut Blockchain, gatekeeper: Arc, dbm: Arc>, server_url: &str, ) -> Responder { + let height = chain.tip().height; + // For the local TxIndex logic to be sound, our index needs to have, at least, IRREVOCABLY_RESOLVED blocks + debug_assert!(height >= IRREVOCABLY_RESOLVED); + + let last_n_blocks = get_last_n_blocks(chain, IRREVOCABLY_RESOLVED as usize).await; + let bitcoin_cli = Arc::new(BitcoindClient::new(server_url, Auth::None).unwrap()); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, tip.deref().height); + let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, height); - Responder::new(carrier, gatekeeper, dbm) + Responder::new(&last_n_blocks, height, carrier, gatekeeper, dbm) } pub(crate) async fn create_watcher( @@ -463,7 +469,7 @@ impl Default for ApiConfig { pub(crate) async fn create_api_with_config( api_config: ApiConfig, ) -> (Arc, BitcoindStopper) { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); let mut chain = Blockchain::default().with_height(START_HEIGHT); let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); @@ -474,7 +480,8 @@ pub(crate) async fn create_api_with_config( EXPIRY_DELTA, dbm.clone(), )); - let responder = create_responder(chain.tip(), gk.clone(), dbm.clone(), bitcoind_mock.url()); + let responder = + create_responder(&mut chain, gk.clone(), dbm.clone(), bitcoind_mock.url()).await; let (watcher, stopper) = create_watcher( &mut chain, Arc::new(responder), @@ -527,43 +534,24 @@ pub(crate) struct BitcoindMock { stopper: BitcoindStopper, } +#[derive(Default)] pub(crate) struct MockOptions { error_code: Option, - block_hash: Option, - height: Option, + in_mempool: bool, } impl MockOptions { - pub fn new(error_code: i64, block_hash: BlockHash, height: usize) -> Self { - Self { - error_code: Some(error_code), - block_hash: Some(block_hash), - height: Some(height), - } - } - - pub fn empty() -> Self { - Self { - error_code: None, - block_hash: None, - height: None, - } - } - pub fn with_error(error_code: i64) -> Self { Self { error_code: Some(error_code), - block_hash: None, - height: None, + in_mempool: false, } } - #[allow(dead_code)] - pub fn with_block(block_hash: BlockHash, height: usize) -> Self { + pub fn in_mempool() -> Self { Self { error_code: None, - block_hash: Some(block_hash), - height: Some(height), + in_mempool: true, } } } @@ -577,15 +565,10 @@ impl BitcoindMock { Err(JsonRpcError::new(JsonRpcErrorCode::ServerError(error))) }); io.add_alias("sendrawtransaction", "error"); + io.add_alias("getrawtransaction", "error"); } else { BitcoindMock::add_sendrawtransaction(&mut io); - } - - if let Some(block_hash) = options.block_hash { - BitcoindMock::add_getrawtransaction(&mut io, block_hash.to_string()); - if let Some(height) = options.height { - BitcoindMock::add_getblockheader(&mut io, block_hash.to_string(), height); - } + BitcoindMock::add_getrawtransaction(&mut io, options.in_mempool); } let server = ServerBuilder::new(io) @@ -606,41 +589,25 @@ impl BitcoindMock { }); } - fn add_getrawtransaction(io: &mut IoHandler, block_hash: String) { + fn add_getrawtransaction(io: &mut IoHandler, in_mempool: bool) { io.add_sync_method("getrawtransaction", move |_params: Params| { - match _params { - Params::Array(x) => match x[1] { - Value::Bool(x) => { - if x { - Ok(serde_json::json!({"hex": TX_HEX, "txid": TXID_HEX, "hash": TXID_HEX, "size": 0, - "vsize": 0, "version": 1, "locktime": 0, "vin": [], "vout": [], "blockhash": block_hash })) - } else { - Ok(Value::String(TX_HEX.to_owned())) + if !in_mempool { + Err(JsonRpcError::new(JsonRpcErrorCode::ServerError(rpc_errors::RPC_INVALID_ADDRESS_OR_KEY as i64))) + } else { + match _params { + Params::Array(x) => match x[1] { + Value::Bool(x) => { + if x { + Ok(serde_json::json!({"hex": TX_HEX, "txid": TXID_HEX, "hash": TXID_HEX, "size": 0, + "vsize": 0, "version": 1, "locktime": 0, "vin": [], "vout": [] })) + } else { + Ok(Value::String(TX_HEX.to_owned())) + } } - } - _ => panic!("Boolean param not found"), - }, - _ => panic!("No params found"), - } - }) - } - - fn add_getblockheader(io: &mut IoHandler, block_hash: String, height: usize) { - io.add_sync_method("getblockheader", move |_params: Params| { - match _params { - Params::Array(x) => match x[1] { - Value::Bool(x) => { - if x { - Ok(serde_json::json!({"hash": block_hash, "confirmations": 1, "height": height, "version": 1, - "merkleroot": "4eca41cf0fa551346842eb317564a403e39553444790a65f949f95bc18d24643", "time": 1645719068, "nonce": 2, "bits": "207fffff", - "difficulty": 0.0, "chainwork": "0000000000000000000000000000000000000000000000000000000000001146", "nTx": 1})) - } else { - Ok(Value::String(TX_HEX.to_owned())) - } - } - _ => panic!("Boolean param not found"), - }, - _ => panic!("No params found"), + _ => panic!("Boolean param not found"), + }, + _ => panic!("No params found"), + } } }) } diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index d08050d..5d8b958 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -771,10 +771,10 @@ mod tests { use crate::rpc_errors; use crate::test_utils::{ create_carrier, create_responder, create_watcher, generate_dummy_appointment, - generate_dummy_appointment_with_user, generate_uuid, get_last_n_blocks, get_random_breach, - get_random_tx, store_appointment_and_fks_to_db, BitcoindMock, BitcoindStopper, Blockchain, - MockOptions, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, - START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + generate_dummy_appointment_with_user, generate_uuid, get_random_breach, get_random_tx, + store_appointment_and_fks_to_db, BitcoindMock, BitcoindStopper, Blockchain, MockOptions, + MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, START_HEIGHT, + SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, }; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::dbm::Error as DBError; @@ -782,7 +782,7 @@ mod tests { use bitcoin::hash_types::Txid; use bitcoin::hashes::Hash; use bitcoin::secp256k1::{PublicKey, Secp256k1}; - use bitcoin::Block; + use lightning::chain::Listen; impl PartialEq for Watcher { @@ -820,7 +820,7 @@ mod tests { chain: &mut Blockchain, dbm: Arc>, ) -> (Watcher, BitcoindStopper) { - let bitcoind_mock = BitcoindMock::new(MockOptions::empty()); + let bitcoind_mock = BitcoindMock::new(MockOptions::default()); let gk = Arc::new(Gatekeeper::new( chain.get_block_count(), @@ -829,7 +829,7 @@ mod tests { EXPIRY_DELTA, dbm.clone(), )); - let responder = create_responder(chain.tip(), gk.clone(), dbm.clone(), bitcoind_mock.url()); + let responder = create_responder(chain, gk.clone(), dbm.clone(), bitcoind_mock.url()).await; create_watcher( chain, Arc::new(responder), From 221358d6d453670c4fc3a1bf5d651db2c21930cb Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 7 Oct 2022 13:06:05 +0200 Subject: [PATCH 040/119] Updates docs regarding txindex --- DEPENDENCIES.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 8b1c12f..4efa204 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -19,7 +19,6 @@ You can get Bitcoin Core from [bitcoincore.org](https://bitcoincore.org/en/downl Bitcoin needs to be running with the following options enabled: -- `txindex` to be able to look for non-wallet transactions - `server` to run rpc commands Here's an example of a `bitcoin.conf` you can use for mainnet. **DO NOT USE THE PROVIDED RPC USER AND PASSWORD.** @@ -31,9 +30,6 @@ rpcuser=user rpcpassword=passwd rpcservertimeout=600 -# [blockchain] -txindex=1 - # [others] daemon=1 debug=1 From a20065567f98d77aba1446c20b932a6e15339d4a Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 8 Nov 2022 13:38:33 +0100 Subject: [PATCH 041/119] Changes rpc commands naming from snake case to lower case --- teos/src/cli_config.rs | 2 +- watchtower-plugin/tests/test.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/teos/src/cli_config.rs b/teos/src/cli_config.rs index 0a942db..ba085b8 100644 --- a/teos/src/cli_config.rs +++ b/teos/src/cli_config.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use structopt::StructOpt; #[derive(Debug, StructOpt, Clone)] -#[structopt(rename_all = "snake_case")] +#[structopt(rename_all = "lower_case")] pub enum Command { /// Gets information about all appointments stored in the tower GetAllAppointments, diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 735675f..9050bf3 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -29,7 +29,7 @@ def test_watchtower(node_factory, bitcoind, teosd): l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) # We need to register l2 with the tower - tower_id = teosd.cli.get_tower_info()["tower_id"] + tower_id = teosd.cli.gettowerinfo()["tower_id"] l2.rpc.registertower(tower_id) # Force a new commitment @@ -94,7 +94,7 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): ) # We need to register l2 with the tower - tower_id = teosd.cli.get_tower_info()["tower_id"] + tower_id = teosd.cli.gettowerinfo()["tower_id"] l2.rpc.registertower(tower_id) # Stop the tower @@ -120,7 +120,7 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): ) # We need to register l2 with the tower - tower_id = teosd.cli.get_tower_info()["tower_id"] + tower_id = teosd.cli.gettowerinfo()["tower_id"] l2.rpc.registertower(tower_id) # Stop the tower @@ -158,7 +158,7 @@ def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": "watchtower-client", "allow_broken_log": True}]) # We need to register l2 with the tower - tower_id = teosd.cli.get_tower_info()["tower_id"] + tower_id = teosd.cli.gettowerinfo()["tower_id"] l2.rpc.registertower(tower_id) # Restart overwriting the tower private key @@ -175,7 +175,7 @@ def test_get_appointment(node_factory, bitcoind, teosd, directory): l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) # We need to register l2 with the tower - tower_id = teosd.cli.get_tower_info()["tower_id"] + tower_id = teosd.cli.gettowerinfo()["tower_id"] l2.rpc.registertower(tower_id) # Force a new commitment From 12f8b48f5738a8a57e8dc99efb321e105b76f72e Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Sat, 12 Nov 2022 09:47:05 -0300 Subject: [PATCH 042/119] Tracks Cargo.lock Rationale: https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html --- .github/workflows/cln-plugin.yaml | 4 +- .gitignore | 1 - Cargo.lock | 3876 +++++++++++++++++++++++++++++ 3 files changed, 3878 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 6550dd3..36c5074 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -60,8 +60,8 @@ jobs: cd lightning && sudo make install - name: Install teos and the plugin run: | - cargo install --path teos - cargo install --path watchtower-plugin + cargo install --locked --path teos + cargo install --locked --path watchtower-plugin - name: Add test dependencies run: | cd watchtower-plugin/tests diff --git a/.gitignore b/.gitignore index a8c94ca..3b7e0f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ target __pycache__ -Cargo.lock .vscode .idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b99a1d5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3876 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.5", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2 0.4.4", + "waker-fn", + "winapi 0.3.9", +] + +[[package]] +name = "async-lock" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-object-pool" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite 0.2.8", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.5", + "instant", + "pin-project-lite 0.2.8", + "rand 0.8.5", + "tokio 1.20.1", +] + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "basic-cookies" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + +[[package]] +name = "bech32" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitcoin" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05bba324e6baf655b882df672453dbbc527bc938cadd27750ae510aaccc3a66a" +dependencies = [ + "base64-compat", + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0e67dbf7a9971e7f4276f6089e9e814ce0f624a03216b7d92d00351ae7fb3e" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e2ae16202721ba8c3409045681fac790a5ddc791f05731a2df22c0c6bffc0f1" +dependencies = [ + "bitcoin", + "serde", + "serde_json", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blocking" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08493fa7707effc63254c66c6ea908675912493cd67952eda23c09fae2610b1" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6547abe025f4027edacd9edaa357aded014eecec42a5070d9b885c3c334aba2" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cln-plugin" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2043841c090a404cb81b145c8ad3c66bae122ba722387fc322b93c157d596433" +dependencies = [ + "anyhow", + "bytes 1.1.0", + "cln-rpc", + "futures", + "log", + "serde", + "serde_json", + "tokio 1.20.1", + "tokio-stream", + "tokio-util 0.6.9", +] + +[[package]] +name = "cln-rpc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb71ceca239c83a06fb494028b4a6b7b38ad4dd9c0410b7ea6013b90e15045" +dependencies = [ + "anyhow", + "bytes 1.1.0", + "futures-util", + "hex", + "log", + "native-tls", + "serde", + "serde_json", + "tokio 1.20.1", + "tokio-util 0.6.9", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "curl" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.4", + "winapi 0.3.9", +] + +[[package]] +name = "curl-sys" +version = "0.4.55+curl-7.83.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi 0.3.9", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der-oid-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" +dependencies = [ + "num-bigint", + "num-traits", + "syn", +] + +[[package]] +name = "der-parser" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cddf120f700b411b2b02ebeb7f04dc0b7c8835909a6c2f52bf72ed0dd3433b2" +dependencies = [ + "der-oid-macro", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite 0.2.8", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.8", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 0.2.25", + "tokio-util 0.3.1", + "tracing", + "tracing-futures", +] + +[[package]] +name = "h2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes 1.1.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 1.20.1", + "tokio-util 0.6.9", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes 1.1.0", + "headers-core", + "http", + "httpdate 1.0.2", + "mime", + "sha-1 0.10.0", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes 1.1.0", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.6", + "http", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes 1.1.0", + "http", + "pin-project-lite 0.2.8", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "httpmock" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c159c4fc205e6c1a9b325cb7ec135d13b5f47188ce175dabb76ec847f331d9bd" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-trait", + "base64", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.18", + "isahc", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio 1.20.1", + "url", +] + +[[package]] +name = "hyper" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" +dependencies = [ + "bytes 0.5.6", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.2.7", + "http", + "http-body 0.3.1", + "httparse", + "httpdate 0.3.2", + "itoa 0.4.8", + "pin-project", + "socket2 0.3.19", + "tokio 0.2.25", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "httparse", + "httpdate 1.0.2", + "itoa 1.0.1", + "pin-project-lite 0.2.8", + "socket2 0.4.4", + "tokio 1.20.1", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.18", + "pin-project-lite 0.2.8", + "tokio 1.20.1", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.1.0", + "hyper 0.14.18", + "native-tls", + "tokio 1.20.1", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8423b78fc94d12ef1a4a9d13c348c9a78766dda0cc18817adf0faf77e670c8" +dependencies = [ + "base64-compat", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-core" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4467ab6dfa369b69e52bd0692e480c4d117410538526a57a304a0f2250fd95e" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-http-server" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522a047cac0958097ee71d047dd71cb84979fd2fa21c7a68fbe12736bef870a2" +dependencies = [ + "futures", + "hyper 0.13.10", + "jsonrpc-core", + "jsonrpc-server-utils", + "log", + "net2", + "parking_lot", + "unicase", +] + +[[package]] +name = "jsonrpc-server-utils" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce68fa279a2822b3619369cd024f8a4f8e5ce485468834f8679a3c7919aae2d" +dependencies = [ + "bytes 0.5.6", + "futures", + "globset", + "jsonrpc-core", + "lazy_static", + "log", + "tokio 0.2.25", + "tokio-util 0.3.1", + "unicase", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" +dependencies = [ + "regex", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lightning" +version = "0.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d885bf509066af86ae85354c8959028ad6192c22a2657ef8271e94029d30f9d0" +dependencies = [ + "bitcoin", +] + +[[package]] +name = "lightning-block-sync" +version = "0.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f1ed50f41785af19f5cd1225b668e87ef0d59bb84e6f8ef2542933e6082a2c" +dependencies = [ + "bitcoin", + "chunked_transfer", + "futures", + "lightning", + "serde", + "serde_json", +] + +[[package]] +name = "lightning-net-tokio" +version = "0.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" +dependencies = [ + "bitcoin", + "lightning", + "tokio 1.20.1", +] + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if 1.0.0", + "value-bag", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] +name = "oid-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" +dependencies = [ + "der-parser", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "111.22.0+1.1.1q" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +dependencies = [ + "autocfg", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "pem" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +dependencies = [ + "base64", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "winapi 0.3.9", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" +dependencies = [ + "bytes 1.1.0", + "prost-derive 0.8.0", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes 1.1.0", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost-build" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +dependencies = [ + "bytes 1.1.0", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost 0.9.0", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes 1.1.0", + "prost 0.9.0", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rcgen" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7" +dependencies = [ + "chrono", + "pem", + "ring", + "x509-parser", + "yasna", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.5", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes 1.1.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "hyper 0.14.18", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite 0.2.8", + "serde", + "serde_json", + "serde_urlencoded", + "tokio 1.20.1", + "tokio-native-tls", + "tokio-socks", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rusqlite" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustversion" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "indexmap", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "similar" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" + +[[package]] +name = "simple_logger" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea" +dependencies = [ + "atty", + "colored", + "log", + "time", + "winapi 0.3.9", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "string_cache" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "teos" +version = "0.1.2" +dependencies = [ + "bitcoin", + "bitcoincore-rpc", + "hex", + "home", + "jsonrpc-http-server", + "lightning", + "lightning-block-sync", + "lightning-net-tokio", + "log", + "prost 0.9.0", + "rand 0.8.5", + "rcgen", + "rusqlite", + "serde", + "serde_json", + "simple_logger", + "structopt", + "tempdir", + "teos-common", + "tokio 1.20.1", + "tokio-stream", + "toml", + "tonic 0.6.2", + "tonic-build", + "torut", + "triggered", + "warp", +] + +[[package]] +name = "teos-common" +version = "0.1.2" +dependencies = [ + "bitcoin", + "chacha20poly1305", + "hex", + "lightning", + "prost 0.9.0", + "rand 0.8.5", + "rusqlite", + "serde", + "serde_json", + "tonic 0.6.2", + "tonic-build", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi 0.3.9", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa 1.0.1", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "memchr", + "mio 0.6.23", + "num_cpus", + "pin-project-lite 0.1.12", + "slab", +] + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "bytes 1.1.0", + "libc", + "memchr", + "mio 0.8.4", + "num_cpus", + "once_cell", + "pin-project-lite 0.2.8", + "signal-hook-registry", + "socket2 0.4.4", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite 0.2.8", + "tokio 1.20.1", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.20.1", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio 1.20.1", + "webpki", +] + +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio 1.20.1", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.8", + "tokio 1.20.1", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio 1.20.1", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.12", + "tokio 0.2.25", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.2.8", + "tokio 1.20.1", +] + +[[package]] +name = "tokio-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.2.8", + "tokio 1.20.1", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796c5e1cd49905e65dd8e700d4cb1dffcbfdb4fc9d017de08c1a537afd83627c" +dependencies = [ + "async-stream", + "async-trait", + "base64", + "bytes 1.1.0", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "hyper 0.14.18", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.8.0", + "prost-derive 0.8.0", + "tokio 1.20.1", + "tokio-rustls", + "tokio-stream", + "tokio-util 0.6.9", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +dependencies = [ + "async-stream", + "async-trait", + "base64", + "bytes 1.1.0", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "http-body 0.4.4", + "hyper 0.14.18", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.9.0", + "prost-derive 0.9.0", + "tokio 1.20.1", + "tokio-rustls", + "tokio-stream", + "tokio-util 0.6.9", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +dependencies = [ + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "torut" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69" +dependencies = [ + "base32", + "base64", + "derive_more", + "ed25519-dalek", + "hex", + "hmac", + "rand 0.7.3", + "serde", + "serde_derive", + "sha2", + "sha3", + "tokio 1.20.1", +] + +[[package]] +name = "tower" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite 0.2.8", + "rand 0.8.5", + "slab", + "tokio 1.20.1", + "tokio-util 0.7.0", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.8", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "triggered" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce148eae0d1a376c1b94ae651fc3261d9cb8294788b962b7382066376503a2d1" + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha-1 0.9.8", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.18", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio 1.20.1", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.9", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "watchtower-plugin" +version = "0.1.2" +dependencies = [ + "backoff", + "bitcoin", + "cln-plugin", + "hex", + "home", + "httpmock", + "log", + "reqwest", + "rusqlite", + "serde", + "serde_json", + "tempdir", + "teos-common", + "tokio 1.20.1", + "tonic 0.5.2", +] + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "which" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "x509-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +dependencies = [ + "base64", + "chrono", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", +] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "chrono", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] From ecaba32e3e5ebcb5ccbca1891f313670ddf192b5 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 11 Nov 2022 19:30:06 -0300 Subject: [PATCH 043/119] Fixes tor proxy redirection Traffic routed trough Tor was being redirected to localhost indistinguishably of whether the public http API was being offered there or not. This made the tower unavailable (trough Tor) when it was being offered both in clearnet and Tor. --- teos/src/api/tor.rs | 8 ++------ teos/src/main.rs | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/teos/src/api/tor.rs b/teos/src/api/tor.rs index 194a513..55c7cfe 100644 --- a/teos/src/api/tor.rs +++ b/teos/src/api/tor.rs @@ -35,7 +35,7 @@ async fn store_tor_key(key: &TorSecretKeyV3, path: PathBuf) { /// Expose an onion service that re-directs to the public api. pub async fn expose_onion_service( tor_control_port: u16, - api_port: u16, + api_endpoint: SocketAddr, onion_port: u16, path: PathBuf, service_ready: Trigger, @@ -83,11 +83,7 @@ pub async fn expose_onion_service( false, false, None, - &mut [( - onion_port, - format!("127.0.0.1:{}", api_port).parse().unwrap(), - )] - .iter(), + &mut [(onion_port, api_endpoint)].iter(), ) .await .map_err(|e| { diff --git a/teos/src/main.rs b/teos/src/main.rs index cb0bfca..7fdd158 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -339,13 +339,12 @@ async fn main() { if conf.tor_support { log::info!("Starting up Tor hidden service"); let tor_control_port = conf.tor_control_port; - let api_port = conf.api_port; let onion_port = conf.onion_hidden_service_port; tor_task = Some(task::spawn(async move { if let Err(e) = tor::expose_onion_service( tor_control_port, - api_port, + http_api_addr, onion_port, path_network, tor_service_ready, From c4b5fd1eb1470210a33eb28e8939ece1e923b6f6 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 10 Nov 2022 10:34:34 -0300 Subject: [PATCH 044/119] Changes the onion hidden service port to match the clearnet API port Turns out both the clearnet API and the Tor hidden service can be run on the same port. --- teos/src/conf_template.toml | 2 +- teos/src/config.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/teos/src/conf_template.toml b/teos/src/conf_template.toml index dea1b7b..9540cb4 100644 --- a/teos/src/conf_template.toml +++ b/teos/src/conf_template.toml @@ -2,7 +2,7 @@ api_bind = "127.0.0.1" api_port = 9814 tor_control_port = 9051 -onion_hidden_service_port = 2121 +onion_hidden_service_port = 9814 tor_support = false # RPC diff --git a/teos/src/config.rs b/teos/src/config.rs index 6142513..01e2b75 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -106,7 +106,7 @@ pub struct Opt { #[structopt(long)] pub tor_control_port: Option, - /// Port for the onion hidden service to listen on [default: 2121] + /// Port for the onion hidden service to listen on [default: 9814] #[structopt(long)] pub onion_hidden_service_port: Option, } @@ -257,7 +257,7 @@ impl Default for Config { api_port: 9814, tor_support: false, tor_control_port: 9051, - onion_hidden_service_port: 2121, + onion_hidden_service_port: 9814, rpc_bind: "127.0.0.1".into(), rpc_port: 8814, btc_network: "mainnet".into(), From d212aae0da8b411ed1236970222ee27975d75f3a Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Sat, 12 Nov 2022 18:18:10 +0000 Subject: [PATCH 045/119] use --locked with cargo install Using --locked with cargo install(s) will instruct cargo to use the lock file in the repo (Cargo.lock). So all the users' builds will be identical to builds in the master branch. Signed-off-by: Omer Yacine --- INSTALL.md | 2 +- watchtower-plugin/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index e340a11..35d422f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,7 +5,7 @@ The tower can be installed and tested using cargo: ``` git clone https://github.com/talaia-labs/rust-teos.git cd rust-teos -cargo install --path teos +cargo install --locked --path teos ``` You can run tests with: diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index 82f8d3f..f0738d9 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -25,7 +25,7 @@ The plugin also has an implicit method to send appointments to the registered to The first step to add the plugin to CLN is installing it. To do so you need to run (from the `rust-teos` folder): ``` -cargo install --path watchtower-plugin +cargo install --locked --path watchtower-plugin ``` That will generate a binary called `watchtower-client`. That's the binary we need to link to CLN. From 6c6d423054f33b4c47457ba4fef1e6d45f4db7bc Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 16 Nov 2022 13:00:16 -0600 Subject: [PATCH 046/119] Minimal changes to make the plugin compatible with cln-plugin 0.1.1 --- .github/workflows/cln-plugin.yaml | 2 +- Cargo.lock | 59 +++++++++++++++----- watchtower-plugin/Cargo.toml | 2 +- watchtower-plugin/src/main.rs | 77 ++++++++++++++------------ watchtower-plugin/tests/conftest.py | 6 +- watchtower-plugin/tests/pyproject.toml | 2 +- watchtower-plugin/tests/test.py | 14 +++-- 7 files changed, 102 insertions(+), 60 deletions(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 36c5074..b31482d 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -4,7 +4,7 @@ on: [push, pull_request] env: bitcoind_version: 0.20.1 - cln_version: 0.11.0.1 + cln_version: 0.12.1 jobs: cache-cln: diff --git a/Cargo.lock b/Cargo.lock index b99a1d5..a902c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,13 +546,14 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2043841c090a404cb81b145c8ad3c66bae122ba722387fc322b93c157d596433" +checksum = "4bb53d11b6ca3ecd28804c12ec7473700a21e2d4c2d17f5af8ed35bf18bce7e8" dependencies = [ "anyhow", "bytes 1.1.0", "cln-rpc", + "env_logger", "futures", "log", "serde", @@ -564,16 +565,17 @@ dependencies = [ [[package]] name = "cln-rpc" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb71ceca239c83a06fb494028b4a6b7b38ad4dd9c0410b7ea6013b90e15045" +checksum = "57be2b864deacdd001c8e5c4e67947e2edbb71f697edf86b7bbb04a66eab3ef4" dependencies = [ "anyhow", + "bitcoin_hashes", "bytes 1.1.0", "futures-util", "hex", "log", - "native-tls", + "secp256k1", "serde", "serde_json", "tokio 1.20.1", @@ -857,6 +859,19 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -1310,6 +1325,12 @@ dependencies = [ "url", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.13.10" @@ -1954,15 +1975,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "111.22.0+1.1.1q" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.74" @@ -1972,7 +1984,6 @@ dependencies = [ "autocfg", "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -2975,6 +2986,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -3759,6 +3779,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index ae864f2..d2421a6 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning bitcoin = "0.28.0" -cln-plugin = "0.1.0" +cln-plugin = "0.1.1" # Local teos-common = { path = "../teos-common" } diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 07c883b..5f25fde 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -520,11 +520,7 @@ async fn main() -> Result<(), Error> { Err(_) => home_dir().unwrap().join(".watchtower"), }; - let (tx, rx) = unbounded_channel(); - let state = Arc::new(Mutex::new(WTClient::new(data_dir, tx).await)); - let state_clone = state.clone(); - - let builder = Builder::new(state, stdin(), stdout()) + let builder = Builder::new(stdin(), stdout()) .option(ConfigOption::new( "watchtower-port", Value::Integer(9814), @@ -537,7 +533,7 @@ async fn main() -> Result<(), Error> { )) .option(ConfigOption::new( "watchtower-proxy", - Value::String(String::new()), + Value::OptString, "Socks v5 proxy IP address and port for the watchtower client", )) .option(ConfigOption::new( @@ -586,41 +582,50 @@ async fn main() -> Result<(), Error> { ) .hook("commitment_revocation", on_commitment_revocation); - if let Some(plugin) = builder.start().await? { - // FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap - // given that we are certain the option exists. - state_clone.lock().unwrap().proxy = - if let Value::String(x) = plugin.option("watchtower-proxy").unwrap() { - if !x.is_empty() { - Some(x) - } else { - None - } + // We're unwrapping here given it does not seem we actually have anything to check at the moment. + // Change this so the plugin can be disabled soon if this happens not to be the case. + let midstate = if let Some(midstate) = builder.configure().await? { + midstate + } else { + return Ok(()); + }; + + let (tx, rx) = unbounded_channel(); + let wt_client = Arc::new(Mutex::new(WTClient::new(data_dir, tx).await)); + // FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap + // given that we are certain the option exists. + wt_client.lock().unwrap().proxy = + if let Value::String(x) = midstate.option("watchtower-proxy").unwrap() { + if !x.is_empty() { + Some(x) } else { None - }; - let max_elapsed_time = - if let Value::Integer(x) = plugin.option("watchtower-max-retry-time").unwrap() { - x as u16 - } else { - // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 900 - }; - let max_interval_time = if let Value::Integer(x) = - plugin.option("dev-watchtower-max-retry-interval").unwrap() - { + } + } else { + None + }; + let max_elapsed_time = + if let Value::Integer(x) = midstate.option("watchtower-max-retry-time").unwrap() { x as u16 } else { // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 60 + 900 }; - tokio::spawn(async move { - RetryManager::new(state_clone, rx, max_elapsed_time, max_interval_time) - .manage_retry() - .await - }); - plugin.join().await + let max_interval_time = if let Value::Integer(x) = midstate + .option("dev-watchtower-max-retry-interval") + .unwrap() + { + x as u16 } else { - Ok(()) - } + // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. + 60 + }; + + let plugin = midstate.start(wt_client.clone()).await?; + tokio::spawn(async move { + RetryManager::new(wt_client, rx, max_elapsed_time, max_interval_time) + .manage_retry() + .await + }); + plugin.join().await } diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index 3a97a67..f93a8de 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -81,7 +81,11 @@ class TeosD(TailableProc): if overwrite_key: self.cmd_line.append("--overwritekey") TailableProc.start(self) - self.wait_for_log("Tower ready", timeout=TIMEOUT) + # FIXME: Temporarily removing this because I cannot figure out why some times the TailableProc cannot find the + # proper logline even if it is there. This normally happens after stopping and starting the TaibleProc, which + # made me think that re-initializing it may work (TailableProc.__init(...)) given that re-sets the logs and the + # offset, but this also fails some times. I don't think it is work wasting much more time here atm. + # self.wait_for_log("Tower ready", timeout=TIMEOUT) logging.info("TeosD started") def stop(self): diff --git a/watchtower-plugin/tests/pyproject.toml b/watchtower-plugin/tests/pyproject.toml index c81b8b0..2760baa 100644 --- a/watchtower-plugin/tests/pyproject.toml +++ b/watchtower-plugin/tests/pyproject.toml @@ -12,7 +12,7 @@ black = "^22.6.0" [tool.poetry.dev-dependencies] pytest = "^7.1.2" pytest-timeout = "^2.1.0" -pyln-testing = "^0.10.2" +pyln-testing = "^0.12.1" [build-system] diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 9050bf3..49cdaac 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -1,5 +1,9 @@ +import os import time import pytest +from pathlib import Path + +WT_PLUGIN = Path("~/.cargo/bin/watchtower-client").expanduser() def change_endianness(x): @@ -26,7 +30,7 @@ def test_watchtower(node_factory, bitcoind, teosd): commitment transaction. """ - l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) + l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": WT_PLUGIN}]) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] @@ -86,7 +90,7 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): opts=[ {}, { - "plugin": "watchtower-client", + "plugin": WT_PLUGIN, "allow_broken_log": True, "dev-watchtower-max-retry-interval": max_interval_time, }, @@ -116,7 +120,7 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): def test_retry_watchtower(node_factory, bitcoind, teosd): # The plugin is set to give up on retrying straight-away so we can test this fast. l1, l2 = node_factory.line_graph( - 2, opts=[{}, {"plugin": "watchtower-client", "allow_broken_log": True, "watchtower-max-retry-time": 0}] + 2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True, "watchtower-max-retry-time": 0}] ) # We need to register l2 with the tower @@ -155,7 +159,7 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): - l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": "watchtower-client", "allow_broken_log": True}]) + l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True}]) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] @@ -172,7 +176,7 @@ def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): def test_get_appointment(node_factory, bitcoind, teosd, directory): - l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": "watchtower-client"}]) + l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": WT_PLUGIN}]) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] From f63738e038ad6f87b581cde4e71836183831bd8d Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 24 Nov 2022 17:12:11 +0100 Subject: [PATCH 047/119] Adds a fixed sleep so `TeosD` has time to bootstrap in the `watchtower-plugin` tests This is a hotfix and should be properly fix --- watchtower-plugin/tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index f93a8de..4eccf61 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -1,5 +1,6 @@ import os import json +import time import pytest import logging import subprocess @@ -86,6 +87,7 @@ class TeosD(TailableProc): # made me think that re-initializing it may work (TailableProc.__init(...)) given that re-sets the logs and the # offset, but this also fails some times. I don't think it is work wasting much more time here atm. # self.wait_for_log("Tower ready", timeout=TIMEOUT) + time.sleep(2) logging.info("TeosD started") def stop(self): From 441a37155d14134310e214822740ed3d10a02ef5 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Sat, 12 Nov 2022 15:37:38 -0300 Subject: [PATCH 048/119] Revamps tor.rs so the onion address can be queried --- teos/src/api/tor.rs | 271 ++++++++++++++++++++++++-------------------- 1 file changed, 149 insertions(+), 122 deletions(-) diff --git a/teos/src/api/tor.rs b/teos/src/api/tor.rs index 55c7cfe..0664272 100644 --- a/teos/src/api/tor.rs +++ b/teos/src/api/tor.rs @@ -5,131 +5,150 @@ use std::path::PathBuf; use tokio::fs; use tokio::net::TcpStream; -use tokio::time::{sleep, Duration}; use torut::control::UnauthenticatedConn; use torut::onion::TorSecretKeyV3; use triggered::{Listener, Trigger}; -/// Loads a Tor key from disk (if found). -async fn load_tor_key(path: PathBuf) -> Option { - log::info!("Loading Tor secret key from disk"); - let key = fs::read(path.join("onion_v3_sk")) - .await - .map_err(|e| log::warn!("Tor secret key cannot be loaded. {}", e)) - .ok()?; - let key: [u8; 64] = key - .try_into() - .map_err(|_| log::error!("Cannot convert loaded data into Tor secret key")) - .ok()?; - - Some(TorSecretKeyV3::from(key)) -} - -/// Stores a Tor key to disk. -async fn store_tor_key(key: &TorSecretKeyV3, path: PathBuf) { - if let Err(e) = fs::write(path.join("onion_v3_sk"), key.as_bytes()).await { - log::error!("Cannot store Tor secret key. {}", e); - } -} - -/// Expose an onion service that re-directs to the public api. -pub async fn expose_onion_service( - tor_control_port: u16, +pub struct TorAPI { + sk: TorSecretKeyV3, api_endpoint: SocketAddr, onion_port: u16, - path: PathBuf, - service_ready: Trigger, - shutdown_signal_tor: Listener, -) -> Result<(), Error> { - let stream = connect_tor_cp(format!("127.0.0.1:{}", tor_control_port).parse().unwrap()) - .await - .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; + tor_control_port: u16, +} - let mut unauth_conn = UnauthenticatedConn::new(stream); +impl TorAPI { + pub async fn new( + api_endpoint: SocketAddr, + onion_port: u16, + tor_control_port: u16, + path: PathBuf, + ) -> Self { + let key = if let Some(key) = TorAPI::load_sk(path.clone()).await { + key + } else { + log::info!("Generating fresh Tor secret key"); + let key = TorSecretKeyV3::generate(); + TorAPI::store_sk(&key, path).await; + key + }; - let pre_auth = unauth_conn - .load_protocol_info() - .await - .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; - - let auth_data = pre_auth - .make_auth_data()? - .expect("failed to make auth data"); - - unauth_conn.authenticate(&auth_data).await.map_err(|_| { - Error::new( - ErrorKind::PermissionDenied, - "failed to authenticate with Tor", - ) - })?; - - let mut auth_conn = unauth_conn.into_authenticated().await; - - auth_conn.set_async_event_handler(Some(|_| async move { Ok(()) })); - - let key = if let Some(key) = load_tor_key(path.clone()).await { - key - } else { - log::info!("Generating fresh Tor secret key"); - let key = TorSecretKeyV3::generate(); - store_tor_key(&key, path).await; - key - }; - - auth_conn - .add_onion_v3( - &key, - false, - false, - false, - None, - &mut [(onion_port, api_endpoint)].iter(), - ) - .await - .map_err(|e| { - Error::new( - ErrorKind::Other, - format!("failed to create onion hidden service: {}", e), - ) - })?; - - print_onion_service(key.clone(), onion_port); - service_ready.trigger(); - - // NOTE: Needed to keep connection with control port & hidden service running, as soon as we leave - // this function the control port stream is dropped and the hidden service is killed - loop { - sleep(Duration::from_secs(1)).await; - if shutdown_signal_tor.is_triggered() { - break; + Self { + sk: key, + api_endpoint, + onion_port, + tor_control_port, } } - auth_conn - .del_onion( - &key.public() - .get_onion_address() - .get_address_without_dot_onion(), - ) - .await - .unwrap(); - Ok(()) -} + pub fn get_onion_address(&self) -> String { + self.sk.public().get_onion_address().to_string() + } -async fn connect_tor_cp(addr: SocketAddr) -> Result { - let sock = TcpStream::connect(addr).await.map_err(|_| { - Error::new( - ErrorKind::ConnectionRefused, - "failed to connect to Tor control port", - ) - })?; - Ok(sock) -} + /// Loads a Tor key from disk (if found). + async fn load_sk(path: PathBuf) -> Option { + log::info!("Loading Tor secret key from disk"); + let key = fs::read(path.join("onion_v3_sk")) + .await + .map_err(|e| log::warn!("Tor secret key cannot be loaded. {}", e)) + .ok()?; + let key: [u8; 64] = key + .try_into() + .map_err(|_| log::error!("Cannot convert loaded data into Tor secret key")) + .ok()?; -fn print_onion_service(key: TorSecretKeyV3, onion_port: u16) { - let onion_addr = key.public().get_onion_address(); - let onion = format!("{}:{}", onion_addr, onion_port); - log::info!("Onion service: {}", onion); + Some(TorSecretKeyV3::from(key)) + } + + /// Stores a Tor key to disk. + async fn store_sk(key: &TorSecretKeyV3, path: PathBuf) { + if let Err(e) = fs::write(path.join("onion_v3_sk"), key.as_bytes()).await { + log::error!("Cannot store Tor secret key. {}", e); + } + } + + /// Tries to connect to the Tor control port + async fn connect_tor_cp(&self) -> Result { + let sock = TcpStream::connect(format!("127.0.0.1:{}", self.tor_control_port)) + .await + .map_err(|_| { + Error::new( + ErrorKind::ConnectionRefused, + "failed to connect to Tor control port", + ) + })?; + Ok(sock) + } + + /// Expose an onion service that re-directs to the public api. + pub async fn expose_onion_service( + &self, + service_ready: Trigger, + shutdown_signal_tor: Listener, + ) -> Result<(), Error> { + let stream = self + .connect_tor_cp() + .await + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; + + let mut unauth_conn = UnauthenticatedConn::new(stream); + + let pre_auth = unauth_conn + .load_protocol_info() + .await + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; + + let auth_data = pre_auth + .make_auth_data()? + .expect("failed to make auth data"); + + unauth_conn.authenticate(&auth_data).await.map_err(|_| { + Error::new( + ErrorKind::PermissionDenied, + "failed to authenticate with Tor", + ) + })?; + + let mut auth_conn = unauth_conn.into_authenticated().await; + + auth_conn.set_async_event_handler(Some(|_| async move { Ok(()) })); + + auth_conn + .add_onion_v3( + &self.sk, + false, + false, + false, + None, + &mut [(self.onion_port, self.api_endpoint)].iter(), + ) + .await + .map_err(|e| { + Error::new( + ErrorKind::Other, + format!("failed to create onion hidden service: {}", e), + ) + })?; + + log::info!( + "Onion service: {}:{}", + self.get_onion_address(), + self.onion_port + ); + service_ready.trigger(); + shutdown_signal_tor.await; + + auth_conn + .del_onion( + &self + .sk + .public() + .get_onion_address() + .get_address_without_dot_onion(), + ) + .await + .unwrap(); + Ok(()) + } } #[cfg(test)] @@ -140,40 +159,48 @@ mod tests { use teos_common::test_utils::get_random_user_id; #[tokio::test] - async fn test_store_load_key() { + async fn test_store_load_sk() { let key = TorSecretKeyV3::generate(); let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); - store_tor_key(&key, tmp_path.path().into()).await; - let loaded_key = load_tor_key(tmp_path.path().into()).await; + TorAPI::store_sk(&key, tmp_path.path().into()).await; + let loaded_key = TorAPI::load_sk(tmp_path.path().into()).await; assert_eq!(key, loaded_key.unwrap()) } #[tokio::test] - async fn test_load_key_inexistent() { + async fn test_load_sk_inexistent() { let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); - let loaded_key = load_tor_key(tmp_path.path().into()).await; + let loaded_key = TorAPI::load_sk(tmp_path.path().into()).await; assert_eq!(loaded_key, None); } #[tokio::test] - async fn test_load_key_wrong_format() { + async fn test_load_sk_wrong_format() { let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); fs::write(tmp_path.path().join("onion_v3_sk"), "random stuff") .await .unwrap(); - let loaded_key = load_tor_key(tmp_path.path().into()).await; + let loaded_key = TorAPI::load_sk(tmp_path.path().into()).await; assert_eq!(loaded_key, None); } #[tokio::test] async fn test_connect_tor_cp_fail() { - let tor_control_port = 9000; - let addr = format!("127.0.0.1:{}", tor_control_port).parse().unwrap(); - match connect_tor_cp(addr).await { + let wrong_cp = 9000; + let tmp_path = TempDir::new(&format!("data_dir_{}", get_random_user_id())).unwrap(); + let tor_api = TorAPI::new( + "127.0.1.1:9814".parse().unwrap(), + 9814, + wrong_cp, + tmp_path.path().into(), + ) + .await; + + match tor_api.connect_tor_cp().await { Ok(_) => {} Err(e) => { assert_eq!("failed to connect to Tor control port", e.to_string()) From 0c1b4c17b4a1ca7ba4887eec51e6ffedd0018405 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Sat, 12 Nov 2022 14:33:34 -0300 Subject: [PATCH 049/119] Adds addresses to gettowerinfo --- teos-common/src/lib.rs | 1 + teos-common/src/net.rs | 39 ++++++++++++++++ teos/build.rs | 4 ++ teos/proto/teos/v2/tower_services.proto | 13 +++++- teos/src/api/internal.rs | 9 ++++ teos/src/api/mod.rs | 1 + teos/src/api/serde.rs | 62 +++++++++++++++++++++++++ teos/src/main.rs | 54 ++++++++++++++------- teos/src/test_utils.rs | 2 + 9 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 teos-common/src/net.rs create mode 100644 teos/src/api/serde.rs diff --git a/teos-common/src/lib.rs b/teos-common/src/lib.rs index a4d5cc5..b355e5b 100644 --- a/teos-common/src/lib.rs +++ b/teos-common/src/lib.rs @@ -13,6 +13,7 @@ pub mod constants; pub mod cryptography; pub mod dbm; pub mod errors; +pub mod net; pub mod receipts; pub mod ser; pub mod test_utils; diff --git a/teos-common/src/net.rs b/teos-common/src/net.rs new file mode 100644 index 0000000..e0497a7 --- /dev/null +++ b/teos-common/src/net.rs @@ -0,0 +1,39 @@ +use std::fmt; + +/// Represents all types of teos network addresses +pub enum AddressType { + IpV4 = 0, + TorV3 = 1, +} + +impl From for AddressType { + fn from(x: i32) -> Self { + match x { + 0 => AddressType::IpV4, + 1 => AddressType::TorV3, + x => panic!("Unknown address type {}", x), + } + } +} + +impl std::str::FromStr for AddressType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ipv4" => Ok(AddressType::IpV4), + "torv3" => Ok(AddressType::TorV3), + _ => Err(format!("Unknown type: {}", s)), + } + } +} + +impl fmt::Display for AddressType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + AddressType::IpV4 => "ipv4", + AddressType::TorV3 => "torv3", + }; + write!(f, "{}", s) + } +} diff --git a/teos/build.rs b/teos/build.rs index 0bd1477..aa9031f 100644 --- a/teos/build.rs +++ b/teos/build.rs @@ -12,6 +12,10 @@ fn main() -> Result<(), Box> { "GetUserResponse.appointments", "#[serde(serialize_with = \"teos_common::ser::serde_vec_bytes::serialize\")]", ) + .field_attribute( + "NetworkAddress.address_type", + "#[serde(rename = \"type\", with = \"crate::api::serde::serde_address_type\")]", + ) .compile( &[ "proto/teos/v2/appointment.proto", diff --git a/teos/proto/teos/v2/tower_services.proto b/teos/proto/teos/v2/tower_services.proto index 944e2d2..92fa881 100644 --- a/teos/proto/teos/v2/tower_services.proto +++ b/teos/proto/teos/v2/tower_services.proto @@ -7,15 +7,26 @@ import "common/teos/v2/appointment.proto"; import "common/teos/v2/user.proto"; import "google/protobuf/empty.proto"; +message NetworkAddress { + // Tower public API endpoint. + enum AddressType { + IpV4 = 0; + TorV3 = 1; + } + AddressType address_type = 1; + string address = 2; + uint32 port = 3; + +} message GetTowerInfoResponse { // Response with information about the tower. - bytes tower_id = 1; uint32 n_registered_users = 2; uint32 n_watcher_appointments = 3; uint32 n_responder_trackers = 4; bool bitcoind_reachable = 5; + repeated NetworkAddress addresses = 6; } service PublicTowerServices { diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index e79c427..bcfe5b1 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -21,6 +21,8 @@ use teos_common::UserId; pub struct InternalAPI { /// A [Watcher] instance. watcher: Arc, + /// A list of public API endpoints. + addresses: Vec, /// A flag that indicates wether bitcoind is reachable or not. bitcoind_reachable: Arc<(Mutex, Condvar)>, /// A signal indicating the tower is shuting down. @@ -31,16 +33,22 @@ impl InternalAPI { /// Creates a new [InternalAPI] instance. pub fn new( watcher: Arc, + addresses: Vec, bitcoind_reachable: Arc<(Mutex, Condvar)>, shutdown_trigger: Trigger, ) -> Self { Self { watcher, + addresses, bitcoind_reachable, shutdown_trigger, } } + pub fn get_addresses(&self) -> &Vec { + &self.addresses + } + /// Checks whether bitcoind is reachable. fn check_service_unavailable(&self) -> Result<(), Status> { if *self.bitcoind_reachable.0.lock().unwrap() { @@ -305,6 +313,7 @@ impl PrivateTowerServices for Arc { ) -> Result, Status> { Ok(Response::new(msgs::GetTowerInfoResponse { tower_id: self.watcher.tower_id.to_vec(), + addresses: self.get_addresses().clone(), n_registered_users: self.watcher.get_registered_users_count() as u32, n_watcher_appointments: self.watcher.get_appointments_count() as u32, n_responder_trackers: self.watcher.get_trackers_count() as u32, diff --git a/teos/src/api/mod.rs b/teos/src/api/mod.rs index 281e208..e901b04 100644 --- a/teos/src/api/mod.rs +++ b/teos/src/api/mod.rs @@ -1,3 +1,4 @@ pub mod http; pub mod internal; +pub mod serde; pub mod tor; diff --git a/teos/src/api/serde.rs b/teos/src/api/serde.rs new file mode 100644 index 0000000..d8c2183 --- /dev/null +++ b/teos/src/api/serde.rs @@ -0,0 +1,62 @@ +use crate::protos as msgs; + +use teos_common::net::AddressType; + +impl msgs::NetworkAddress { + pub fn from_ipv4(address: String, port: u16) -> Self { + Self { + address_type: AddressType::IpV4 as i32, + address, + port: port as u32, + } + } + + pub fn from_torv3(address: String, port: u16) -> Self { + Self { + address_type: AddressType::TorV3 as i32, + address, + port: port as u32, + } + } +} + +pub mod serde_address_type { + use serde::de::{self, Deserializer}; + use serde::Serializer; + use std::str::FromStr; + + use super::AddressType; + + pub fn serialize(status: &i32, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&AddressType::from(*status).to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct StatusVisitor; + + impl<'de> de::Visitor<'de> for StatusVisitor { + type Value = i32; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string containing the address type") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + let status = AddressType::from_str(v) + .map_err(|_| E::custom("given address type is unknown"))?; + Ok(status as i32) + } + } + + deserializer.deserialize_any(StatusVisitor) + } +} diff --git a/teos/src/main.rs b/teos/src/main.rs index 7fdd158..1986310 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -19,13 +19,14 @@ use lightning_block_sync::poll::{ use lightning_block_sync::{BlockSource, SpvClient, UnboundedCache}; use teos::api::internal::InternalAPI; -use teos::api::{http, tor}; +use teos::api::{http, tor::TorAPI}; use teos::bitcoin_cli::BitcoindClient; use teos::carrier::Carrier; use teos::chain_monitor::ChainMonitor; use teos::config::{self, Config, Opt}; use teos::dbm::DBM; use teos::gatekeeper::Gatekeeper; +use teos::protos as msgs; use teos::protos::private_tower_services_server::PrivateTowerServicesServer; use teos::protos::public_tower_services_server::PublicTowerServicesServer; use teos::responder::Responder; @@ -273,8 +274,36 @@ async fn main() { log::info!("Bootstrap completed. Turning on interfaces"); // Build interfaces + let http_api_addr = format!("{}:{}", conf.api_bind, conf.api_port) + .parse() + .unwrap(); + let mut addresses = vec![msgs::NetworkAddress::from_ipv4( + conf.api_bind.clone(), + conf.api_port, + )]; + + // Create Tor endpoint if required + let tor_api = if conf.tor_support { + let tor_api = TorAPI::new( + http_api_addr, + conf.onion_hidden_service_port, + conf.tor_control_port, + path_network, + ) + .await; + addresses.push(msgs::NetworkAddress::from_torv3( + tor_api.get_onion_address(), + conf.api_port, + )); + + Some(tor_api) + } else { + None + }; + let rpc_api = Arc::new(InternalAPI::new( watcher, + addresses, bitcoind_reachable.clone(), shutdown_trigger, )); @@ -290,9 +319,6 @@ async fn main() { "http://{}:{}", conf.internal_api_bind, conf.internal_api_port ); - let http_api_addr = format!("{}:{}", conf.api_bind, conf.api_port) - .parse() - .unwrap(); // Generate mtls certificates to data directory so the admin can securely connect // to the server to perform administrative tasks. @@ -336,21 +362,13 @@ async fn main() { // Add Tor Onion Service for public API let mut tor_task = Option::None; let (tor_service_ready, ready_signal_tor) = triggered::trigger(); - if conf.tor_support { + if let Some(tor_api) = tor_api { log::info!("Starting up Tor hidden service"); - let tor_control_port = conf.tor_control_port; - let onion_port = conf.onion_hidden_service_port; tor_task = Some(task::spawn(async move { - if let Err(e) = tor::expose_onion_service( - tor_control_port, - http_api_addr, - onion_port, - path_network, - tor_service_ready, - shutdown_signal_tor, - ) - .await + if let Err(e) = tor_api + .expose_onion_service(tor_service_ready, shutdown_signal_tor) + .await { eprintln!("Cannot connect to the Tor backend: {}", e); std::process::exit(1); @@ -367,8 +385,8 @@ async fn main() { http_api_task.await.unwrap(); private_api_task.await.unwrap(); public_api_task.await.unwrap(); - if conf.tor_support { - tor_task.unwrap().await.unwrap(); + if let Some(tor_task) = tor_task { + tor_task.await.unwrap(); } log::info!("Shutting down tower"); diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index f8cdf7a..b7959fd 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -45,6 +45,7 @@ use crate::carrier::Carrier; use crate::dbm::DBM; use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::{Gatekeeper, UserInfo}; +use crate::protos as msgs; use crate::responder::{ConfirmationStatus, Responder, TransactionTracker}; use crate::rpc_errors; use crate::watcher::{Breach, Watcher}; @@ -496,6 +497,7 @@ pub(crate) async fn create_api_with_config( ( Arc::new(InternalAPI::new( Arc::new(watcher), + vec![msgs::NetworkAddress::from_ipv4("address".to_string(), 21)], bitcoind_reachable, shutdown_trigger, )), From 246511d0bc78d73a0d219a88f6fbec28eb2c5614 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 30 Nov 2022 10:31:33 +0100 Subject: [PATCH 050/119] Removes redundant sanity check for Retrier::start --- watchtower-plugin/src/retrier.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 51f6b14..8887fa1 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -235,12 +235,6 @@ impl Retrier { return Err(Error::permanent("Tower was abandoned. Skipping retry")); } - if !self.has_pending_appointments() { - // will this ever happen ?? - // FIXME: success/Ok() here instead so not to mark the tower as unreachable. - return Err(Error::permanent("Tower has no data pending for retry")); - } - let net_addr = wt_client .towers .get(&self.tower_id) @@ -778,10 +772,7 @@ mod tests { // If there are no pending appointments the method will simply return let r = Retrier::empty(wt_client, tower_id).run().await; - assert_eq!( - r, - Err(Error::permanent("Tower has no data pending for retry")) - ); + assert_eq!(r, Ok(())); } #[tokio::test] From da3daddba8867805fd9951434026fc8ee72ab82c Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 8 Dec 2022 05:50:20 -0500 Subject: [PATCH 051/119] Avoids setting the state of unregistered towers --- watchtower-plugin/src/main.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 5f25fde..f1f3ca6 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -96,14 +96,13 @@ async fn register( ) }) .map_err(|e| { - if e.is_connection() { - plugin - .state() - .lock() - .unwrap() - .set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); + let mut state = plugin.state().lock().unwrap(); + if e.is_connection() && state.towers.contains_key(&tower_id) { + state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); } - to_cln_error(e) + let e = to_cln_error(e); + log::info!("{}", e); + e })?; if !receipt.verify(&tower_id) { From 5177446629c395342c515f19a8fe07713f10d4af Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 8 Dec 2022 09:10:31 -0500 Subject: [PATCH 052/119] Replaces sleep based waiting for log entry lookups Replaces time.sleep in the `watchtower-plugin` tests to wait for certain things to happen on the backend by wait_for_logs, which should be less error prone Also does some reformating and removes unnecessary imports --- watchtower-plugin/tests/conftest.py | 7 ++--- watchtower-plugin/tests/test.py | 42 ++++++++--------------------- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index 4eccf61..ace3627 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -1,13 +1,10 @@ -import os -import json -import time -import pytest -import logging +from pathlib import Path import subprocess from pyln.testing.fixtures import * # noqa: F401,F403 from pyln.testing.utils import DEVELOPER, BITCOIND_CONFIG, TIMEOUT, TailableProc +WT_PLUGIN = Path("~/.cargo/bin/watchtower-client").expanduser() TEOSD_CONFIG = { "btc_network": "regtest", "polling_delta": 0, diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 49cdaac..6f25a83 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -1,9 +1,5 @@ -import os -import time import pytest -from pathlib import Path - -WT_PLUGIN = Path("~/.cargo/bin/watchtower-client").expanduser() +from conftest import WT_PLUGIN def change_endianness(x): @@ -58,8 +54,8 @@ def test_watchtower(node_factory, bitcoind, teosd): assert l2.rpc.getappointment(tower_id, locator)["status"] == "being_watched" # Confirm the dispute so the tower can react with the penalty - bitcoind.generate_block(1) - time.sleep(1) + bitcoind.generate_block() + l1.daemon.wait_for_log("State changed from FUNDING_SPEND_SEEN to ONCHAIN") penalty_txid = bitcoind.rpc.getrawmempool()[0] # The channel still exists between the two peers, but it's on chain @@ -68,8 +64,7 @@ def test_watchtower(node_factory, bitcoind, teosd): # Generate blocks until the penalty gets irrevocably resolved for i in range(101): - bitcoind.generate_block(1) - time.sleep(0.1) + bitcoind.generate_block() if i < 100: assert l2.rpc.getappointment(tower_id, locator)["status"] == "dispute_responded" else: @@ -109,10 +104,9 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): assert l2.rpc.gettowerinfo(tower_id)["status"] == "temporary_unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] - # Start the tower and check the automatic backoff works (wait while are pending appointments) + # Start the tower and check the automatic backoff works teosd.start() - while l2.rpc.gettowerinfo(tower_id)["pending_appointments"]: - time.sleep(1) + l2.daemon.wait_for_log(f"Retry strategy succeeded for {tower_id}") assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" @@ -133,28 +127,16 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): # Make a new payment with an unreachable tower l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) - # The retrier manager waits 1 second before spawning new retriers for unreachable towers, - # so we need to wait a little bit until a retrier is started for our tower. - while l2.rpc.gettowerinfo(tower_id)["status"] == "temporary_unreachable": - time.sleep(1) + # Wait until the tower has been flagged as unreachable + l2.daemon.wait_for_log(f"Setting {tower_id} as unreachable") assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] # Start the tower and retry it teosd.start() - # Even though we set the max retry time to zero seconds, the retrier manager takes some time (1s) to recognize - # that the tower is unreachable. So manual retries might fail as the tower is marked as temporary unreachable. - while True: - try: - l2.rpc.retrytower(tower_id) - break - except Exception: - time.sleep(1) - - while l2.rpc.gettowerinfo(tower_id)["pending_appointments"]: - time.sleep(1) - + l2.rpc.retrytower(tower_id) + l2.daemon.wait_for_log(f"Retry strategy succeeded for {tower_id}") assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" @@ -197,10 +179,8 @@ def test_get_appointment(node_factory, bitcoind, teosd, directory): appointment = l2.rpc.getappointment(tower_id, locator)["appointment"] assert "locator" in appointment and "encrypted_blob" in appointment and "to_self_delay" in appointment - bitcoind.generate_block(1) - time.sleep(1) - # And after. Now this should be a tracker + bitcoind.generate_block() tracker = l2.rpc.getappointment(tower_id, locator)["appointment"] assert "dispute_txid" in tracker and "penalty_txid" in tracker and "penalty_rawtx" in tracker From 14176bd31d2f5da7c39a72ab589ed4639a365baa Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 9 Dec 2022 17:10:30 -0600 Subject: [PATCH 053/119] Fixes #156 `TeosD` outputDir was being set to the same directory use by the test suite. That cause it to be logging in the same log file, potentially colluding for read/write operations. Setting the outputDir to it's own location fixed the issue. --- watchtower-plugin/tests/conftest.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index ace3627..b0aed48 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -2,7 +2,7 @@ from pathlib import Path import subprocess from pyln.testing.fixtures import * # noqa: F401,F403 -from pyln.testing.utils import DEVELOPER, BITCOIND_CONFIG, TIMEOUT, TailableProc +from pyln.testing.utils import DEVELOPER, BITCOIND_CONFIG, TailableProc WT_PLUGIN = Path("~/.cargo/bin/watchtower-client").expanduser() TEOSD_CONFIG = { @@ -56,9 +56,9 @@ class TeosCLI: class TeosD(TailableProc): def __init__(self, bitcoind_rpcport, directory="/tmp/watchtower-test"): - TailableProc.__init__(self, directory, verbose=True) self.teos_dir = os.path.join(directory, "teos") self.prefix = "teosd" + TailableProc.__init__(self, self.teos_dir) self.cli = TeosCLI(directory) if not os.path.exists(self.teos_dir): @@ -79,16 +79,14 @@ class TeosD(TailableProc): if overwrite_key: self.cmd_line.append("--overwritekey") TailableProc.start(self) - # FIXME: Temporarily removing this because I cannot figure out why some times the TailableProc cannot find the - # proper logline even if it is there. This normally happens after stopping and starting the TaibleProc, which - # made me think that re-initializing it may work (TailableProc.__init(...)) given that re-sets the logs and the - # offset, but this also fails some times. I don't think it is work wasting much more time here atm. - # self.wait_for_log("Tower ready", timeout=TIMEOUT) - time.sleep(2) + self.wait_for_log("Tower ready") + logging.info("TeosD started") def stop(self): self.cli.stop() + self.wait_for_log("Shutting down tower") + return TailableProc.stop(self) From 1e295e1661811cc9c734b0643014fd38888c7f4a Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Sat, 22 Oct 2022 09:23:00 +0200 Subject: [PATCH 054/119] Replace server.address() by server.base_url() Signed-off-by: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> --- watchtower-plugin/src/net/http.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 9cb17e4..fff1d78 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -221,7 +221,7 @@ mod tests { let (response, receipt) = add_appointment( TowerId(tower_pk), - &format!("http://{}", server.address()), + &server.base_url(), None, &appointment, appointment_receipt.user_signature(), @@ -253,7 +253,7 @@ mod tests { let (response, receipt) = send_appointment( TowerId(tower_pk), - &format!("http://{}", server.address()), + &server.base_url(), None, &appointment, appointment_receipt.user_signature(), @@ -286,7 +286,7 @@ mod tests { let tower_id = get_random_user_id(); let error = send_appointment( tower_id, - &format!("http://{}", server.address()), + &server.base_url(), None, &appointment, appointment_receipt.user_signature(), @@ -340,7 +340,7 @@ mod tests { let error = send_appointment( get_random_user_id(), - &format!("http://{}", server.address()), + &server.base_url(), None, &generate_random_appointment(None), "user_sig", @@ -373,7 +373,7 @@ mod tests { let error = send_appointment( get_random_user_id(), - &format!("http://{}", server.address()), + &server.base_url(), None, &generate_random_appointment(None), "user_sig", @@ -393,7 +393,7 @@ mod tests { then.status(200).header("content-type", "application/json"); }); - let response = post_request(&format!("http://{}", server.address()), json!(""), None) + let response = post_request(&server.base_url(), json!(""), None) .await .unwrap(); @@ -425,7 +425,7 @@ mod tests { // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( - post_request(&format!("http://{}", server.address()), json!(""), None).await, + post_request(&server.base_url(), json!(""), None).await, ) .await .unwrap_err(); From f7d144c6f39ccb0031a478ec2a3e56398b35b4de Mon Sep 17 00:00:00 2001 From: Jonathan Plasse <13716151+JonathanPlasse@users.noreply.github.com> Date: Thu, 13 Oct 2022 19:02:49 +0200 Subject: [PATCH 055/119] Refactor to avoid using `SELECT *` --- teos/src/dbm.rs | 60 ++++++++++++++++++++++++------------ watchtower-plugin/src/dbm.rs | 20 ++++++------ 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 8b43500..db1636f 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -165,7 +165,10 @@ impl DBM { /// Loads all users from the database. pub(crate) fn load_all_users(&self) -> HashMap { let mut users = HashMap::new(); - let mut stmt = self.connection.prepare("SELECT * FROM users").unwrap(); + let mut stmt = self + .connection + .prepare("SELECT user_id, available_slots, subscription_start, subscription_expiry FROM users") + .unwrap(); let mut rows = stmt.query([]).unwrap(); while let Ok(Some(row)) = rows.next() { @@ -278,21 +281,28 @@ impl DBM { let key = uuid.to_vec(); let mut stmt = self .connection - .prepare("SELECT * FROM appointments WHERE UUID=(?)") + .prepare( + "SELECT locator, encrypted_blob, to_self_delay, user_signature, start_block, user_id + FROM appointments WHERE UUID=(?)" + ) .unwrap(); stmt.query_row([key], |row| { - let raw_locator: Vec = row.get(1).unwrap(); - let locator = Locator::from_slice(&raw_locator).unwrap(); - let raw_userid: Vec = row.get(6).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); + let raw_locator: Vec = row.get(0).unwrap(); + let encrypted_blob = row.get(1).unwrap(); + let to_self_delay = row.get(2).unwrap(); + let user_signature = row.get(3).unwrap(); + let start_block = row.get(4).unwrap(); + let raw_userid: Vec = row.get(5).unwrap(); - let appointment = Appointment::new(locator, row.get(2).unwrap(), row.get(3).unwrap()); + let locator = Locator::from_slice(&raw_locator).unwrap(); + let user_id = UserId::from_slice(&raw_userid).unwrap(); + let appointment = Appointment::new(locator, encrypted_blob, to_self_delay); Ok(ExtendedAppointment::new( appointment, user_id, - row.get(4).unwrap(), - row.get(5).unwrap(), + user_signature, + start_block, )) }) .map_err(|_| Error::NotFound) @@ -306,7 +316,9 @@ impl DBM { ) -> HashMap { let mut appointments = HashMap::new(); - let mut sql = "SELECT * FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL".to_string(); + let mut sql = + "SELECT a.UUID, a.locator, a.encrypted_blob, a.to_self_delay, a.user_signature, a.start_block, a.user_id + FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL".to_string(); // If a locator was passed, filter based on it. if locator.is_some() { sql.push_str(" AND a.locator=(?)"); @@ -447,17 +459,22 @@ impl DBM { /// Loads a [TransactionTracker] from the database. pub(crate) fn load_tracker(&self, uuid: UUID) -> Result { let key = uuid.to_vec(); - let mut stmt = self.connection.prepare( - "SELECT t.*, a.user_id FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID WHERE t.UUID=(?)").unwrap(); + let mut stmt = self + .connection.prepare( + "SELECT t.dispute_tx, t.penalty_tx, t.height, t.confirmed, a.user_id + FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID WHERE t.UUID=(?)" + ) + .unwrap(); stmt.query_row([key], |row| { - let raw_dispute_tx: Vec = row.get(1).unwrap(); + let raw_dispute_tx: Vec = row.get(0).unwrap(); + let raw_penalty_tx: Vec = row.get(1).unwrap(); + let height: u32 = row.get(2).unwrap(); + let confirmed: bool = row.get(3).unwrap(); + let raw_userid: Vec = row.get(4).unwrap(); + let dispute_tx = consensus::deserialize(&raw_dispute_tx).unwrap(); - let raw_penalty_tx: Vec = row.get(2).unwrap(); let penalty_tx = consensus::deserialize(&raw_penalty_tx).unwrap(); - let height: u32 = row.get(3).unwrap(); - let confirmed: bool = row.get(4).unwrap(); - let raw_userid: Vec = row.get(5).unwrap(); let user_id = UserId::from_slice(&raw_userid).unwrap(); Ok(TransactionTracker { @@ -478,7 +495,9 @@ impl DBM { ) -> HashMap { let mut trackers = HashMap::new(); - let mut sql = "SELECT t.*, a.user_id FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID".to_string(); + let mut sql = "SELECT t.UUID, t.dispute_tx, t.penalty_tx, t.height, t.confirmed, a.user_id + FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID" + .to_string(); // If a locator was passed, filter based on it. if locator.is_some() { sql.push_str(" WHERE a.locator=(?)"); @@ -593,7 +612,10 @@ mod tests { let key = user_id.to_vec(); let mut stmt = self .connection - .prepare("SELECT * FROM users WHERE user_id=(?)") + .prepare( + "SELECT user_id, available_slots, subscription_start, subscription_expiry + FROM users WHERE user_id=(?)", + ) .unwrap(); let user = stmt .query_row([&key], |row| { diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 84a7398..43e4788 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -220,7 +220,7 @@ impl DBM { let mut stmt = self .connection .prepare( - "SELECT * + "SELECT available_slots, subscription_start, subscription_expiry, signature FROM registration_receipts WHERE tower_id = ?1 AND subscription_expiry = (SELECT MAX(subscription_expiry) FROM registration_receipts @@ -230,10 +230,10 @@ impl DBM { let receipt = stmt .query_row([tower_id.to_vec()], |row| { - let slots: u32 = row.get(1).unwrap(); - let start: u32 = row.get(2).unwrap(); - let expiry: u32 = row.get(3).unwrap(); - let signature: String = row.get(4).unwrap(); + let slots: u32 = row.get(0).unwrap(); + let start: u32 = row.get(1).unwrap(); + let expiry: u32 = row.get(2).unwrap(); + let signature: String = row.get(3).unwrap(); Ok(RegistrationReceipt::with_signature( user_id, slots, start, expiry, signature, @@ -333,13 +333,13 @@ impl DBM { ) -> Result { let mut stmt = self .connection - .prepare("SELECT * FROM appointment_receipts WHERE tower_id = ?1 and locator = ?2") + .prepare("SELECT start_block, user_signature, tower_signature FROM appointment_receipts WHERE tower_id = ?1 and locator = ?2") .unwrap(); stmt.query_row(params![tower_id.to_vec(), locator.to_vec()], |row| { - let start_block = row.get::<_, u32>(2).unwrap(); - let user_sig = row.get::<_, String>(3).unwrap(); - let tower_sig = row.get::<_, String>(4).unwrap(); + let start_block = row.get::<_, u32>(0).unwrap(); + let user_sig = row.get::<_, String>(1).unwrap(); + let tower_sig = row.get::<_, String>(2).unwrap(); Ok(AppointmentReceipt::with_signature( user_sig, @@ -549,7 +549,7 @@ impl DBM { let mut appointments = Vec::new(); let mut stmt = self .connection - .prepare(&format!("SELECT * FROM appointments as a, {} as t WHERE a.locator = t.locator AND t.tower_id = ?", table)) + .prepare(&format!("SELECT a.locator, a.encrypted_blob, a.to_self_delay FROM appointments as a, {} as t WHERE a.locator = t.locator AND t.tower_id = ?", table)) .unwrap(); let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); From f7153c49330e7eb7f34158d1da0fdaebbbd6f8d0 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 24 Oct 2022 12:12:56 +0200 Subject: [PATCH 056/119] Replaces net_addr params from String to &str --- watchtower-plugin/src/main.rs | 2 +- watchtower-plugin/src/retrier.rs | 20 ++++++++++---------- watchtower-plugin/src/wt_client.rs | 30 +++++++++++++++--------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f1f3ca6..d4d4d5c 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -115,7 +115,7 @@ async fn register( .state() .lock() .unwrap() - .add_update_tower(tower_id, tower_net_addr, &receipt).map_err(|e| { + .add_update_tower(tower_id, &tower_net_addr, &receipt).map_err(|e| { if e.is_expiry() { anyhow!("Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for") } else { diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 8887fa1..7973677 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -400,7 +400,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Add appointment to pending @@ -537,7 +537,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Add appointment to pending @@ -616,7 +616,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Add appointment to pending @@ -682,7 +682,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) @@ -720,7 +720,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Add appointment to pending @@ -767,7 +767,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // If there are no pending appointments the method will simply return @@ -790,7 +790,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Add appointment to pending @@ -868,7 +868,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); let api_mock = server.mock(|when, then| { @@ -911,7 +911,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); let api_mock = server.mock(|when, then| { @@ -962,7 +962,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, server.base_url(), &receipt) + .add_update_tower(tower_id, &server.base_url(), &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 353323b..7b1a466 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -83,7 +83,7 @@ impl WTClient { pub fn add_update_tower( &mut self, tower_id: TowerId, - tower_net_addr: String, + tower_net_addr: &str, receipt: &RegistrationReceipt, ) -> Result<(), SubscriptionError> { if let Some(tower) = self.towers.get(&tower_id) { @@ -108,7 +108,7 @@ impl WTClient { self.towers.insert( tower_id, TowerSummary::new( - tower_net_addr, + tower_net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -277,7 +277,7 @@ mod tests { ); wt_client - .add_update_tower(tower_id, tower_info.net_addr.clone(), &receipt) + .add_update_tower(tower_id, &tower_info.net_addr, &receipt) .unwrap(); assert_eq!( wt_client.towers.get(&tower_id), @@ -295,7 +295,7 @@ mod tests { receipt.subscription_expiry(), ); wt_client - .add_update_tower(tower_id, updated_tower_info.net_addr.clone(), &receipt) + .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt) .unwrap(); assert_eq!( @@ -322,19 +322,19 @@ mod tests { ); assert!(matches!( - wt_client.add_update_tower(tower_id, updated_tower_info.net_addr.clone(), &receipt), + wt_client.add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt), Err(SubscriptionError::Expiry) )); + assert!(matches!( + wt_client.add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt_same_slots), + Err(SubscriptionError::Slots) + )); assert!(matches!( wt_client.add_update_tower( tower_id, - updated_tower_info.net_addr.clone(), - &receipt_same_slots + &updated_tower_info.net_addr, + &receipt_same_expiry ), - Err(SubscriptionError::Slots) - )); - assert!(matches!( - wt_client.add_update_tower(tower_id, updated_tower_info.net_addr, &receipt_same_expiry), Err(SubscriptionError::Expiry) )); } @@ -713,7 +713,7 @@ mod tests { // Add the tower and check it is there wt_client - .add_update_tower(tower_id, tower_info.net_addr.clone(), &receipt) + .add_update_tower(tower_id, &tower_info.net_addr, &receipt) .unwrap(); assert_eq!( wt_client.towers.get(&tower_id), @@ -731,7 +731,7 @@ mod tests { // Try again but this time with an associated appointment to check that it also gets removed wt_client - .add_update_tower(tower_id, tower_info.net_addr, &receipt) + .add_update_tower(tower_id, &tower_info.net_addr, &receipt) .unwrap(); let locator = generate_random_appointment(None).locator; @@ -779,10 +779,10 @@ mod tests { receipt.subscription_expiry(), ); wt_client - .add_update_tower(tower1_id, tower_info.net_addr.clone(), &receipt) + .add_update_tower(tower1_id, &tower_info.net_addr, &receipt) .unwrap(); wt_client - .add_update_tower(tower2_id, tower_info.net_addr, &receipt) + .add_update_tower(tower2_id, &tower_info.net_addr, &receipt) .unwrap(); let locator = generate_random_appointment(None).locator; From b5f9a1e03755da0ededc3ee0a8d864336e5cfc9b Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 24 Oct 2022 12:34:58 +0200 Subject: [PATCH 057/119] into() -> to_owned() for Strings `to_owned` is more explicit. --- watchtower-plugin/src/convert.rs | 19 +++++------ watchtower-plugin/src/dbm.rs | 38 +++++++++++----------- watchtower-plugin/src/lib.rs | 16 ++++----- watchtower-plugin/src/main.rs | 2 +- watchtower-plugin/src/net/http.rs | 22 +++++++------ watchtower-plugin/src/retrier.rs | 10 +++--- watchtower-plugin/src/wt_client.rs | 52 ++++++++++++++---------------- 7 files changed, 79 insertions(+), 80 deletions(-) diff --git a/watchtower-plugin/src/convert.rs b/watchtower-plugin/src/convert.rs index 265fefa..41ef4b2 100644 --- a/watchtower-plugin/src/convert.rs +++ b/watchtower-plugin/src/convert.rs @@ -55,7 +55,7 @@ impl RegisterParams { fn from_id(tower_id: &str) -> Result { Ok(Self { tower_id: TowerId::from_str(tower_id) - .map_err(|_| RegisterError::InvalidId("Invalid tower id".into()))?, + .map_err(|_| RegisterError::InvalidId("Invalid tower id".to_owned()))?, host: None, port: None, }) @@ -63,10 +63,10 @@ impl RegisterParams { fn with_host(self, host: &str) -> Result { if host.is_empty() { - Err(RegisterError::InvalidHost("hostname is empty".into())) + Err(RegisterError::InvalidHost("hostname is empty".to_owned())) } else if host.contains(' ') { Err(RegisterError::InvalidHost( - "hostname contains white spaces".into(), + "hostname contains white spaces".to_owned(), )) } else { Ok(Self { @@ -193,21 +193,22 @@ impl TryFrom for GetAppointmentParams { ))) } else { let tower_id = if let Some(s) = a.get(0).unwrap().as_str() { - TowerId::from_str(s) - .map_err(|_| GetAppointmentError::InvalidId("Invalid tower id".into())) + TowerId::from_str(s).map_err(|_| { + GetAppointmentError::InvalidId("Invalid tower id".to_owned()) + }) } else { Err(GetAppointmentError::InvalidId( - "tower_id must be a hex encoded string".into(), + "tower_id must be a hex encoded string".to_owned(), )) }?; let locator = if let Some(s) = a.get(1).unwrap().as_str() { Locator::from_hex(s).map_err(|_| { - GetAppointmentError::InvalidLocator("Invalid locator".into()) + GetAppointmentError::InvalidLocator("Invalid locator".to_owned()) }) } else { Err(GetAppointmentError::InvalidLocator( - "locator must be a hex encoded string".into(), + "locator must be a hex encoded string".to_owned(), )) }?; @@ -262,7 +263,7 @@ mod tests { // Any properly formatted host should work let params = RegisterParams::from_id(VALID_ID).unwrap(); let host = "myhost"; - assert_eq!(params.with_host(host).unwrap().host, Some(host.into())); + assert_eq!(params.with_host(host).unwrap().host, Some(host.to_owned())); // Host must not be empty not have spaces assert!(matches!( diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 43e4788..7206c6d 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -705,7 +705,7 @@ mod tests { let receipt = get_random_registration_receipt(); let tower_info = TowerInfo::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -821,7 +821,7 @@ mod tests { towers.insert( tower_id, TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -872,7 +872,7 @@ mod tests { let receipt = get_random_registration_receipt(); let mut tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -886,9 +886,9 @@ mod tests { let appointment = generate_random_appointment(None); let user_signature = "user_signature"; let appointment_receipt = AppointmentReceipt::with_signature( - user_signature.into(), + user_signature.to_owned(), 42, - "tower_signature".into(), + "tower_signature".to_owned(), ); tower_summary.available_slots -= 1; @@ -935,15 +935,15 @@ mod tests { // Add both let tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), ); let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".into(), + "user_signature".to_owned(), 42, - "tower_signature".into(), + "tower_signature".to_owned(), ); dbm.store_appointment_receipt( tower_id, @@ -971,7 +971,7 @@ mod tests { let receipt = get_random_registration_receipt(); let tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -987,9 +987,9 @@ mod tests { for _ in 0..5 { let appointment = generate_random_appointment(None); let appointment_receipt = AppointmentReceipt::with_signature( - user_signature.into(), + user_signature.to_owned(), 42, - "tower_signature".into(), + "tower_signature".to_owned(), ); let pending_appointment = generate_random_appointment(None); let invalid_appointment = generate_random_appointment(None); @@ -1058,7 +1058,7 @@ mod tests { let receipt = get_random_registration_receipt(); let mut tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -1188,7 +1188,7 @@ mod tests { let receipt = get_random_registration_receipt(); let mut tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -1251,7 +1251,7 @@ mod tests { let receipt = get_random_registration_receipt(); let tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -1266,9 +1266,9 @@ mod tests { // Store a misbehaving proof and load it back let appointment = generate_random_appointment(None); let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".into(), + "user_signature".to_owned(), 42, - "tower_signature".into(), + "tower_signature".to_owned(), ); let proof = MisbehaviorProof::new( @@ -1300,7 +1300,7 @@ mod tests { let receipt = get_random_registration_receipt(); let tower_summary = TowerSummary::new( - net_addr.into(), + net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -1315,9 +1315,9 @@ mod tests { // // Store a misbehaving proof check let appointment = generate_random_appointment(None); let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".into(), + "user_signature".to_owned(), 42, - "tower_signature".into(), + "tower_signature".to_owned(), ); let proof = MisbehaviorProof::new( diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index dc7804c..7c95219 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -312,7 +312,7 @@ mod tests { #[test] fn test_new() { - let net_addr: String = "addr".into(); + let net_addr: String = "addr".to_owned(); let tower_summary = TowerSummary::new( net_addr.clone(), @@ -336,7 +336,7 @@ mod tests { #[test] fn test_with_appointments() { - let net_addr: String = "addr".into(); + let net_addr: String = "addr".to_owned(); let pending_appointments = HashSet::from_iter([generate_random_appointment(None).locator]); @@ -368,7 +368,7 @@ mod tests { #[test] fn test_with_status() { let mut tower_summary = TowerSummary::new( - "addr".into(), + "addr".to_owned(), AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY, @@ -407,7 +407,7 @@ mod tests { #[test] fn test_new() { let tower_info = TowerInfo::new( - "addr".into(), + "addr".to_owned(), AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY, @@ -423,7 +423,7 @@ mod tests { #[test] fn test_with_status() { let mut tower_info = TowerInfo::empty( - "addr".into(), + "addr".to_owned(), AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY, @@ -437,7 +437,7 @@ mod tests { #[test] fn test_set_misbehaving_proof() { let mut tower_info = TowerInfo::empty( - "addr".into(), + "addr".to_owned(), AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY, @@ -445,9 +445,9 @@ mod tests { assert_eq!(tower_info.misbehaving_proof, None); let appointment_receipt = AppointmentReceipt::with_signature( - "user_signature".into(), + "user_signature".to_owned(), SUBSCRIPTION_START + 1, - "tower_signature".into(), + "tower_signature".to_owned(), ); let proof = MisbehaviorProof::new( generate_random_appointment(None).locator, diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index d4d4d5c..a9c0aa2 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -46,7 +46,7 @@ async fn register( v: serde_json::Value, ) -> Result { let params = RegisterParams::try_from(v).map_err(|x| anyhow!(x))?; - let host = params.host.unwrap_or_else(|| "localhost".into()); + let host = params.host.unwrap_or_else(|| "localhost".to_owned()); let tower_id = params.tower_id; let user_id = plugin.state().lock().unwrap().user_id; diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index fff1d78..8faec5f 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -84,7 +84,7 @@ pub async fn send_appointment( ) -> Result<(common_msgs::AddAppointmentResponse, AppointmentReceipt), AddAppointmentError> { let request_data = common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: signature.into(), + signature: signature.to_owned(), }; match process_post_response( @@ -99,7 +99,7 @@ pub async fn send_appointment( { ApiResponse::Response::(r) => { let receipt = AppointmentReceipt::with_signature( - signature.into(), + signature.to_owned(), r.start_block, r.signature.clone(), ); @@ -139,7 +139,7 @@ pub async fn post_request( .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? } else { return Err(RequestError::ConnectionError( - "Cannot connect to an onion address without a proxy".into(), + "Cannot connect to an onion address without a proxy".to_owned(), )); } } else { @@ -149,9 +149,11 @@ pub async fn post_request( client.post(endpoint).json(&data).send().await.map_err(|e| { log::debug!("POST request failed: {:?}", e); if e.is_connect() | e.is_timeout() { - RequestError::ConnectionError("Cannot connect to the tower. Connection refused".into()) + RequestError::ConnectionError( + "Cannot connect to the tower. Connection refused".to_owned(), + ) } else { - RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".into()) + RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".to_owned()) } }) } @@ -187,11 +189,11 @@ mod tests { fn test_is_connection() { let error_message = "error_msg"; for error in [ - RequestError::ConnectionError(error_message.into()), - RequestError::DeserializeError(error_message.into()), - RequestError::Unexpected(error_message.into()), + RequestError::ConnectionError(error_message.to_owned()), + RequestError::DeserializeError(error_message.to_owned()), + RequestError::Unexpected(error_message.to_owned()), ] { - if error == RequestError::ConnectionError(error_message.into()) { + if error == RequestError::ConnectionError(error_message.to_owned()) { assert!(error.is_connection()) } else { assert!(!error.is_connection()) @@ -359,7 +361,7 @@ mod tests { #[tokio::test] async fn test_send_appointment_api_error() { let api_error = ApiError { - error: "error_msg".into(), + error: "error_msg".to_owned(), error_code: 1, }; diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 7973677..40b6bc5 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -475,7 +475,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, "http://unreachable.tower".into(), &receipt) + .add_update_tower(tower_id, "http://unreachable.tower", &receipt) .unwrap(); // Add appointment to pending @@ -553,7 +553,7 @@ mod tests { then.status(400) .header("content-type", "application/json") .json_body(json!(ApiError { - error: "error_msg".into(), + error: "error_msg".to_owned(), error_code: 1, })); }); @@ -836,7 +836,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, "http://unreachable.tower".into(), &receipt) + .add_update_tower(tower_id, "http://unreachable.tower", &receipt) .unwrap(); // Add some pending appointments and try again (with an unreachable tower). @@ -876,7 +876,7 @@ mod tests { then.status(400) .header("content-type", "application/json") .json_body(json!(ApiError { - error: "error_msg".into(), + error: "error_msg".to_owned(), error_code: errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR, })); }); @@ -919,7 +919,7 @@ mod tests { then.status(400) .header("content-type", "application/json") .json_body(json!(ApiError { - error: "error_msg".into(), + error: "error_msg".to_owned(), error_code: 1, })); }); diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 7b1a466..a976594 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -103,7 +103,7 @@ impl WTClient { } self.dbm - .store_tower_record(tower_id, &tower_net_addr, receipt) + .store_tower_record(tower_id, tower_net_addr, receipt) .unwrap(); self.towers.insert( tower_id, @@ -270,7 +270,7 @@ mod tests { let mut receipt = get_random_registration_receipt(); let tower_id = get_random_user_id(); let tower_info = TowerInfo::empty( - "talaia.watch".into(), + "talaia.watch".to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -289,7 +289,7 @@ mod tests { receipt = get_registration_receipt_from_previous(&receipt); let updated_tower_info = TowerInfo::empty( - "talaia.watch".into(), + "talaia.watch".to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -354,7 +354,7 @@ mod tests { let receipt = get_random_registration_receipt(); let tower_id = get_random_user_id(); wt_client - .add_update_tower(tower_id, "talaia.watch".into(), &receipt) + .add_update_tower(tower_id, "talaia.watch", &receipt) .unwrap(); for status in [ @@ -377,7 +377,7 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let locator = generate_random_appointment(None).locator; let registration_receipt = get_random_registration_receipt(); @@ -394,7 +394,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.into(), + tower_net_addr.clone(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -403,7 +403,7 @@ mod tests { Vec::new(), ); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_appointment_receipt( tower_id, @@ -427,7 +427,7 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -438,7 +438,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.into(), + tower_net_addr.clone(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -448,7 +448,7 @@ mod tests { ); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -471,7 +471,7 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -481,7 +481,7 @@ mod tests { // Add the tower to the state and try again wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -503,7 +503,7 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -514,7 +514,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.into(), + tower_net_addr.clone(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -524,7 +524,7 @@ mod tests { ); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_invalid_appointment(tower_id, &appointment); @@ -543,13 +543,13 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -589,20 +589,16 @@ mod tests { let tower_id = get_random_user_id(); let another_tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client - .add_update_tower( - another_tower_id, - tower_net_addr.into(), - ®istration_receipt, - ) + .add_update_tower(another_tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); wt_client.add_pending_appointment(another_tower_id, &appointment); @@ -667,7 +663,7 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tower_net_addr = "talaia.watch"; + let tower_net_addr = "talaia.watch".to_owned(); // If we call this on an unknown tower it will simply do nothing let appointment = generate_random_appointment(None); @@ -679,7 +675,7 @@ mod tests { // // Add the tower to the state and try again let registration_receipt = get_random_registration_receipt(); wt_client - .add_update_tower(tower_id, tower_net_addr.into(), ®istration_receipt) + .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) .unwrap(); wt_client.flag_misbehaving_tower(tower_id, proof.clone()); @@ -705,7 +701,7 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let tower_info = TowerInfo::empty( - "talaia.watch".into(), + "talaia.watch".to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), @@ -773,7 +769,7 @@ mod tests { let tower2_id = TowerId(tower2_pk); let tower_info = TowerInfo::empty( - "talaia.watch".into(), + "talaia.watch".to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), From a6ef6946ab71f7a83987759f7a48f3059f6f382c Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 30 Nov 2022 12:57:31 +0100 Subject: [PATCH 058/119] Implements auto-register for the watchtower-plugin --- teos-common/src/receipts.rs | 2 +- watchtower-plugin/src/dbm.rs | 3 + watchtower-plugin/src/lib.rs | 45 +++++++- watchtower-plugin/src/main.rs | 65 +++++------- watchtower-plugin/src/net/http.rs | 101 +++++++++++++++++- watchtower-plugin/src/retrier.rs | 161 +++++++++++++++++++++++++---- watchtower-plugin/src/wt_client.rs | 28 +++-- 7 files changed, 328 insertions(+), 77 deletions(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 7679648..6e806c2 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -25,7 +25,7 @@ pub struct RegistrationReceipt { available_slots: u32, subscription_start: u32, subscription_expiry: u32, - #[serde(skip)] + #[serde(rename = "subscription_signature")] signature: Option, } diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 7206c6d..1c1a828 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -289,6 +289,9 @@ impl DBM { if self.exists_misbehaving_proof(tower_id) { tower.status = TowerStatus::Misbehaving; } else if !tower.pending_appointments.is_empty() { + // TODO: We could set the status to SubscriptionError here if we checked the state of the subscription + // (using available_slots and expiry). This will be possible once we implement cln rpc queries (which are + // already viable since cln-plugin = "0.1.1"). tower.status = TowerStatus::TemporaryUnreachable; } diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index 7c95219..fd43b33 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -90,6 +90,11 @@ impl TowerStatus { pub fn is_subscription_error(&self) -> bool { *self == TowerStatus::SubscriptionError } + + /// Whether the tower can be manually retried + pub fn is_retryable(&self) -> bool { + self.is_unreachable() || self.is_subscription_error() + } } /// Summarized data associated with a given tower. @@ -268,11 +273,12 @@ mod tests { mod tower_status { use super::*; + use TowerStatus::*; #[test] fn test_is_reachable() { for status in STATUSES { - if status == TowerStatus::Reachable { + if status == Reachable { assert!(status.is_reachable()) } else { assert!(!status.is_reachable()); @@ -280,10 +286,32 @@ mod tests { } } + #[test] + fn test_is_temporary_reachable() { + for status in STATUSES { + if status == TemporaryUnreachable { + assert!(status.is_temporary_unreachable()) + } else { + assert!(!status.is_temporary_unreachable()); + } + } + } + + #[test] + fn test_is_unreachable() { + for status in STATUSES { + if status == Unreachable { + assert!(status.is_unreachable()) + } else { + assert!(!status.is_unreachable()); + } + } + } + #[test] fn test_is_misbehaving() { for status in STATUSES { - if status == TowerStatus::Misbehaving { + if status == Misbehaving { assert!(status.is_misbehaving()) } else { assert!(!status.is_misbehaving()); @@ -294,13 +322,24 @@ mod tests { #[test] fn test_is_subscription_error() { for status in STATUSES { - if status == TowerStatus::SubscriptionError { + if status == SubscriptionError { assert!(status.is_subscription_error()) } else { assert!(!status.is_subscription_error()); } } } + + #[test] + fn test_is_retryable() { + for status in STATUSES { + if status == Unreachable || status == SubscriptionError { + assert!(status.is_retryable()) + } else { + assert!(!status.is_retryable()); + } + } + } } mod tower_summary { diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index a9c0aa2..0272253 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -13,25 +13,25 @@ use cln_plugin::{anyhow, Builder, Error, Plugin}; use teos_common::appointment::{Appointment, Locator}; use teos_common::protos as common_msgs; -use teos_common::receipts::RegistrationReceipt; use teos_common::TowerId; use teos_common::{cryptography, errors}; use watchtower_plugin::convert::{CommitmentRevocation, GetAppointmentParams, RegisterParams}; use watchtower_plugin::net::http::{ - add_appointment, post_request, process_post_response, AddAppointmentError, ApiResponse, - RequestError, + self, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, }; use watchtower_plugin::retrier::RetryManager; use watchtower_plugin::wt_client::WTClient; use watchtower_plugin::TowerStatus; fn to_cln_error(e: RequestError) -> Error { - match e { + let e = match e { RequestError::ConnectionError(e) => anyhow!(e), RequestError::DeserializeError(e) => anyhow!(e), RequestError::Unexpected(e) => anyhow!(e), - } + }; + log::info!("{}", e); + e } /// Registers the client to a given tower. @@ -72,38 +72,15 @@ async fn register( let proxy = plugin.state().lock().unwrap().proxy.clone(); - let register_endpoint = format!("{}/register", tower_net_addr); - log::info!("Registering in the Eye of Satoshi (tower_id={})", tower_id); - - let receipt = process_post_response( - post_request( - ®ister_endpoint, - &common_msgs::RegisterRequest { - user_id: user_id.to_vec(), - }, - proxy, - ) - .await, - ) - .await - .map(|r: common_msgs::RegisterResponse| { - RegistrationReceipt::with_signature( - user_id, - r.available_slots, - r.subscription_start, - r.subscription_expiry, - r.subscription_signature, - ) - }) - .map_err(|e| { - let mut state = plugin.state().lock().unwrap(); - if e.is_connection() && state.towers.contains_key(&tower_id) { - state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); - } - let e = to_cln_error(e); - log::info!("{}", e); - e - })?; + let receipt = http::register(tower_id, user_id, &tower_net_addr, proxy) + .await + .map_err(|e| { + let mut state = plugin.state().lock().unwrap(); + if e.is_connection() && state.towers.contains_key(&tower_id) { + state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); + } + to_cln_error(e) + })?; if !receipt.verify(&tower_id) { return Err(anyhow!( @@ -319,9 +296,9 @@ async fn retry_tower( if let Some(tower) = state.towers.get(&tower_id) { if tower.status.is_temporary_unreachable() { return Err(anyhow!("{} is already being retried", tower_id)); - } else if !tower.status.is_unreachable() { + } else if !tower.status.is_retryable() { return Err(anyhow!( - "Tower status must be unreachable to manually retry", + "Tower status must be unreachable or have a subscription issue to manually retry", )); } @@ -405,8 +382,14 @@ async fn on_commitment_revocation( for (tower_id, net_addr, status) in towers { if status.is_reachable() { - match add_appointment(tower_id, &net_addr, proxy.clone(), &appointment, &signature) - .await + match http::add_appointment( + tower_id, + &net_addr, + proxy.clone(), + &appointment, + &signature, + ) + .await { Ok((slots, receipt)) => { plugin diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 8faec5f..81b26cd 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -4,8 +4,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use teos_common::appointment::Appointment; use teos_common::cryptography; use teos_common::protos as common_msgs; -use teos_common::receipts::AppointmentReceipt; -use teos_common::TowerId; +use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; +use teos_common::{TowerId, UserId}; use crate::MisbehaviorProof; @@ -52,6 +52,36 @@ impl From for AddAppointmentError { } } +/// Handles the logic of interacting with the `register` endpoint of the tower. +pub async fn register( + tower_id: TowerId, + user_id: UserId, + tower_net_addr: &str, + proxy: Option, +) -> Result { + log::info!("Registering in the Eye of Satoshi (tower_id={})", tower_id); + process_post_response( + post_request( + &format!("{}/register", tower_net_addr), + &common_msgs::RegisterRequest { + user_id: user_id.to_vec(), + }, + proxy, + ) + .await, + ) + .await + .map(|r: common_msgs::RegisterResponse| { + RegistrationReceipt::with_signature( + user_id, + r.available_slots, + r.subscription_start, + r.subscription_expiry, + r.subscription_signature, + ) + }) +} + /// Encapsulates the logging and response parsing of sending and appointment to the tower. pub async fn add_appointment( tower_id: TowerId, @@ -179,7 +209,8 @@ mod tests { use crate::test_utils::get_dummy_add_appointment_response; use teos_common::test_utils::{ - generate_random_appointment, get_random_appointment_receipt, get_random_user_id, + generate_random_appointment, get_random_appointment_receipt, + get_random_registration_receipt, get_random_user_id, }; mod request_error { @@ -202,6 +233,70 @@ mod tests { } } + #[tokio::test] + async fn test_register() { + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let mut registration_receipt = get_random_registration_receipt(); + registration_receipt.sign(&tower_sk); + + let server = MockServer::start(); + let api_mock = server.mock(|when, then| { + when.method(POST).path("/register"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!(registration_receipt)); + }); + + let receipt = register( + TowerId(tower_pk), + registration_receipt.user_id(), + &format!("http://{}", server.address()), + None, + ) + .await + .unwrap(); + + api_mock.assert(); + assert_eq!(receipt, registration_receipt); + } + + #[tokio::test] + async fn test_register_connection_error() { + let error = register( + get_random_user_id(), + get_random_user_id(), + "http://server_addr", + None, + ) + .await + .unwrap_err(); + + assert!(matches!(error, RequestError::ConnectionError { .. })) + } + + #[tokio::test] + async fn test_register_deserialize_error() { + let server = MockServer::start(); + let api_mock = server.mock(|when, then| { + when.method(POST).path("/register"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!([])); + }); + + let error = register( + get_random_user_id(), + get_random_user_id(), + &format!("http://{}", server.address()), + None, + ) + .await + .unwrap_err(); + + api_mock.assert(); + assert!(matches!(error, RequestError::DeserializeError { .. })) + } + #[tokio::test] async fn test_add_appointment() { // `add_appointment` is basically a pass trough function for `send_appointment` with some logging and a parse of the outputs diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 40b6bc5..66b7026 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -11,7 +11,7 @@ use teos_common::cryptography; use teos_common::errors; use teos_common::UserId as TowerId; -use crate::net::http::{add_appointment, AddAppointmentError}; +use crate::net::http::{self, AddAppointmentError}; use crate::wt_client::WTClient; pub struct RetryManager { @@ -134,7 +134,7 @@ pub enum RetrierStatus { /// Retrier is currently retrying the tower. If the retrier receives new appointments, it will /// **try** to send them along (but it might not send them). /// - /// If a retrier status is `Running`, then its associated tower is temporary unreachable. + /// If a retrier status is `Running`, then its associated tower is either temporary unreachable or subscription error. Running, /// Retrier failed retrying the tower. Should not be re-started. /// @@ -181,12 +181,23 @@ impl Retrier { // We shouldn't be retrying failed and running retriers. debug_assert_eq!(*self.status.lock().unwrap(), RetrierStatus::Stopped); - // Set the tower as temporary unreachable and the retrier status to running. - self.wt_client - .lock() - .unwrap() - .set_tower_status(self.tower_id, crate::TowerStatus::TemporaryUnreachable); - self.set_status(RetrierStatus::Running); + // When manually retrying the tower may be in either SubscriptionError or Unreachable state. + // Flag this as TemporaryUnreachable only if there is no SubscriptionError. + // Rationale: if there is a subscription error that needs to be handled first, otherwise we'll + // waste a retry cycle with a request that will always fail. + { + let mut state = self.wt_client.lock().unwrap(); + if !state + .towers + .get(&self.tower_id) + .unwrap() + .status + .is_subscription_error() + { + state.set_tower_status(self.tower_id, crate::TowerStatus::TemporaryUnreachable); + } + self.set_status(RetrierStatus::Running); + } tokio::spawn(async move { let r = retry_notify( @@ -229,22 +240,48 @@ impl Retrier { async fn run(&self) -> Result<(), Error<&'static str>> { // Create a new scope so we can get all the data only locking the WTClient once. - let (tower_id, net_addr, user_sk, proxy) = { + let (tower_id, status, net_addr, user_id, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); if wt_client.towers.get(&self.tower_id).is_none() { return Err(Error::permanent("Tower was abandoned. Skipping retry")); } - let net_addr = wt_client - .towers - .get(&self.tower_id) - .unwrap() - .net_addr - .clone(); - let user_sk = wt_client.user_sk; - (self.tower_id, net_addr, user_sk, wt_client.proxy.clone()) + let tower = wt_client.towers.get(&self.tower_id).unwrap(); + ( + self.tower_id, + tower.status, + tower.net_addr.clone(), + wt_client.user_id, + wt_client.user_sk, + wt_client.proxy.clone(), + ) }; + // If the tower state is subscription_error we need to re-register first. If we cannot, then the retry is aborted. + if status.is_subscription_error() { + let receipt = http::register(tower_id, user_id, &net_addr, proxy.clone()) + .await + .map_err(|e| { + log::debug!("Cannot renew registration with tower. Error: {:?}", e); + Error::permanent("Cannot renew registration with tower") + })?; + if !receipt.verify(&tower_id) { + return Err(Error::permanent( + "Registration receipt contains bad signature. Are you using the right tower_id?" + )); + } + self.wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, &net_addr, &receipt).map_err(|e| { + if e.is_expiry() { + Error::permanent("Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for") + } else { + Error::permanent("Registration receipt does not contain more slots than the ones we are currently registered for") + } + })?; + } + while self.has_pending_appointments() { let locators = self.pending_appointments.lock().unwrap().clone(); for locator in locators.into_iter() { @@ -256,7 +293,7 @@ impl Retrier { .load_appointment(locator) .unwrap(); - match add_appointment( + match http::add_appointment( tower_id, &net_addr, proxy.clone(), @@ -295,7 +332,7 @@ impl Retrier { tower_id, crate::TowerStatus::SubscriptionError, ); - return Err(Error::permanent("Subscription error")); + return Err(Error::transient("Subscription error")); } _ => { log::warn!( @@ -359,9 +396,10 @@ mod tests { use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; - use teos_common::receipts::AppointmentReceipt; + use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, + get_registration_receipt_from_previous, }; use crate::net::http::ApiError; @@ -705,6 +743,87 @@ mod tests { task.abort(); } + #[tokio::test] + async fn test_manage_retry_subscription_error() { + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let (tx, rx) = unbounded_channel(); + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); + let server = MockServer::start(); + + // Add a tower with pending appointments + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + let mut registration_receipt = + RegistrationReceipt::new(wt_client.lock().unwrap().user_id, 21, 42, 420); + registration_receipt.sign(&tower_sk); + wt_client + .lock() + .unwrap() + .add_update_tower(tower_id, &server.base_url(), ®istration_receipt) + .unwrap(); + + // Add appointment to pending + let appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &appointment); + + // Mock the add_appointment response (this is right, so after the re-registration the appointments are accepted) + let mut add_appointment_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + add_appointment_receipt.sign(&tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); + let add_appointment_mock = server.mock(|when, then| { + when.method(POST).path("/add_appointment"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!(add_appointment_response)); + }); + + // Mock the re-registration + let mut re_registration_receipt = + get_registration_receipt_from_previous(®istration_receipt); + re_registration_receipt.sign(&tower_sk); + let register_mock = server.mock(|when, then| { + when.method(POST).path("/register"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!(re_registration_receipt)); + }); + + // Set the status as SubscriptionError so we simulate the retrier faced this in a previous round + wt_client + .lock() + .unwrap() + .set_tower_status(tower_id, TowerStatus::SubscriptionError); + + // Start the task and send the tower to the channel for retry + let wt_client_clone = wt_client.clone(); + let task = tokio::spawn(async move { + RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) + .manage_retry() + .await + }); + tx.send((tower_id, appointment.locator)).unwrap(); + + // Wait for the elapsed time and check how the tower status changed + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + let state = wt_client.lock().unwrap(); + let tower = state.towers.get(&tower_id).unwrap(); + assert!(tower.status.is_reachable()); + assert!(tower.pending_appointments.is_empty()); + + register_mock.assert(); + add_appointment_mock.assert(); + task.abort(); + } + #[tokio::test] async fn test_retry_tower() { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -892,7 +1011,7 @@ mod tests { let retrier = Retrier::new(wt_client, tower_id, appointment.locator); let r = retrier.run().await; - assert_eq!(r, Err(Error::permanent("Subscription error"))); + assert_eq!(r, Err(Error::transient("Subscription error"))); api_mock.assert(); } diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index a976594..25b1f7f 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -92,11 +92,8 @@ impl WTClient { if receipt.subscription_expiry() <= tower.subscription_expiry { return Err(SubscriptionError::Expiry); } else { - let previous_receipt = self - .dbm - .load_registration_receipt(tower_id, self.user_id) - .unwrap(); - if receipt.available_slots() <= previous_receipt.available_slots() { + let tower_info = self.dbm.load_tower_record(tower_id).unwrap(); + if receipt.available_slots() <= tower_info.available_slots { return Err(SubscriptionError::Slots); } } @@ -268,7 +265,8 @@ mod tests { // Adding a new tower will add a summary to towers and the full data to the let mut receipt = get_random_registration_receipt(); - let tower_id = get_random_user_id(); + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); let tower_info = TowerInfo::empty( "talaia.watch".to_owned(), receipt.available_slots(), @@ -308,18 +306,20 @@ mod tests { ); // If we try to update without increasing both the end_time and the slots, this will fail - let receipt_same_slots = RegistrationReceipt::new( + let mut receipt_same_slots = RegistrationReceipt::new( receipt.user_id(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry() + 1, ); - let receipt_same_expiry = RegistrationReceipt::new( + receipt_same_slots.sign(&tower_sk); + let mut receipt_same_expiry = RegistrationReceipt::new( receipt.user_id(), receipt.available_slots() + 1, receipt.subscription_start(), receipt.subscription_expiry(), ); + receipt_same_expiry.sign(&tower_sk); assert!(matches!( wt_client.add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt), @@ -337,6 +337,18 @@ mod tests { ), Err(SubscriptionError::Expiry) )); + + // Decrease the slots count (simulate exhaustion) and update with more than the current count it should work + let locator = generate_random_appointment(None).locator; + wt_client.add_appointment_receipt( + tower_id, + locator, + 0, + &get_random_appointment_receipt(tower_sk), + ); + wt_client + .add_update_tower(tower_id, &updated_tower_info.net_addr, &receipt_same_slots) + .unwrap(); } #[tokio::test] From e816d9e15273f976ff7ac77af990a67c5c024843 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 30 Nov 2022 13:11:42 +0100 Subject: [PATCH 059/119] Adds a helper fn to get tower_status from WTClient This helps reducing some of the WTClient is queried only to get the tower status --- watchtower-plugin/src/main.rs | 8 ++++---- watchtower-plugin/src/retrier.rs | 32 ++++++++++-------------------- watchtower-plugin/src/wt_client.rs | 30 +++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 0272253..204939f 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -280,7 +280,7 @@ async fn get_tower_info( // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable // by just checking the data in the database. Ok(json!( - tower_info.with_status(state.towers.get(&tower_id).unwrap().status) + tower_info.with_status(state.get_tower_status(&tower_id).unwrap()) )) } @@ -293,10 +293,10 @@ async fn retry_tower( ) -> Result { let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; let state = plugin.state().lock().unwrap(); - if let Some(tower) = state.towers.get(&tower_id) { - if tower.status.is_temporary_unreachable() { + if let Some(status) = state.get_tower_status(&tower_id) { + if status.is_temporary_unreachable() { return Err(anyhow!("{} is already being retried", tower_id)); - } else if !tower.status.is_retryable() { + } else if !status.is_retryable() { return Err(anyhow!( "Tower status must be unreachable or have a subscription issue to manually retry", )); diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 66b7026..408ba61 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -188,10 +188,8 @@ impl Retrier { { let mut state = self.wt_client.lock().unwrap(); if !state - .towers - .get(&self.tower_id) + .get_tower_status(&self.tower_id) .unwrap() - .status .is_subscription_error() { state.set_tower_status(self.tower_id, crate::TowerStatus::TemporaryUnreachable); @@ -374,8 +372,8 @@ impl Retrier { pub fn set_tower_status_if_failed(&self) { if *self.status.lock().unwrap() == RetrierStatus::Failed { let mut state = self.wt_client.lock().unwrap(); - if let Some(tower) = state.towers.get(&self.tower_id) { - if tower.status.is_temporary_unreachable() { + if let Some(status) = state.get_tower_status(&self.tower_id) { + if status.is_temporary_unreachable() { log::warn!("Setting {} as unreachable", self.tower_id); state.set_tower_status(self.tower_id, crate::TowerStatus::Unreachable); } @@ -478,10 +476,8 @@ mod tests { wt_client .lock() .unwrap() - .towers - .get(&tower_id) - .unwrap() - .status, + .get_tower_status(&tower_id) + .unwrap(), TowerStatus::Reachable ); assert!(!wt_client @@ -539,10 +535,8 @@ mod tests { assert!(wt_client .lock() .unwrap() - .towers - .get(&tower_id) + .get_tower_status(&tower_id) .unwrap() - .status .is_temporary_unreachable()); // Wait until the task gives up and check again @@ -550,10 +544,8 @@ mod tests { assert!(wt_client .lock() .unwrap() - .towers - .get(&tower_id) + .get_tower_status(&tower_id) .unwrap() - .status .is_unreachable()); task.abort(); @@ -611,10 +603,8 @@ mod tests { wt_client .lock() .unwrap() - .towers - .get(&tower_id) - .unwrap() - .status, + .get_tower_status(&tower_id) + .unwrap(), TowerStatus::Reachable ); assert!(!wt_client @@ -694,10 +684,8 @@ mod tests { assert!(wt_client .lock() .unwrap() - .towers - .get(&tower_id) + .get_tower_status(&tower_id) .unwrap() - .status .is_misbehaving()); api_mock.assert(); diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 25b1f7f..175182f 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -128,6 +128,11 @@ impl WTClient { self.dbm.load_tower_record(tower_id) } + /// Gets the given tower status (identified by tower_id), if found. + pub fn get_tower_status(&self, tower_id: &TowerId) -> Option { + Some(self.towers.get(tower_id)?.status) + } + /// Sets the tower status to any of the `TowerStatus` variants. pub fn set_tower_status(&mut self, tower_id: TowerId, status: TowerStatus) { if let Some(tower) = self.towers.get_mut(&tower_id) { @@ -351,6 +356,29 @@ mod tests { .unwrap(); } + #[tokio::test] + async fn test_get_tower_status() { + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let mut wt_client = + WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; + + // If the tower is unknown, get_tower_status returns None + let tower_id = get_random_user_id(); + assert!(wt_client.get_tower_status(&tower_id).is_none()); + + // Add a tower + let receipt = get_random_registration_receipt(); + wt_client + .add_update_tower(tower_id, "talaia.watch", &receipt) + .unwrap(); + + // If the tower is known, get_tower_status matches getting the same data from the towers collection + assert_eq!( + wt_client.towers.get(&tower_id).unwrap().status, + wt_client.get_tower_status(&tower_id).unwrap() + ) + } + #[tokio::test] async fn test_set_tower_status() { let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); @@ -377,7 +405,7 @@ mod tests { TowerStatus::Misbehaving, ] { wt_client.set_tower_status(tower_id, status); - assert_eq!(status, wt_client.towers.get(&tower_id).unwrap().status); + assert_eq!(status, wt_client.get_tower_status(&tower_id).unwrap()); } } From d0a476d4af393a15ffedccaaa1409d92f7f4d923 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 19 Oct 2022 17:40:45 +0200 Subject: [PATCH 060/119] Adds named arguments to CoreLN plugin --- teos-common/src/lib.rs | 102 +++++++++++++++++++ watchtower-plugin/src/convert.rs | 163 ++++++++++++++++++++++++++---- watchtower-plugin/src/net/http.rs | 2 +- 3 files changed, 249 insertions(+), 18 deletions(-) diff --git a/teos-common/src/lib.rs b/teos-common/src/lib.rs index b355e5b..7c8b621 100644 --- a/teos-common/src/lib.rs +++ b/teos-common/src/lib.rs @@ -22,6 +22,7 @@ use std::fmt; use std::{convert::TryFrom, str::FromStr}; use serde::{Deserialize, Serialize}; +use serde_json::json; use bitcoin::secp256k1::{Error, PublicKey}; @@ -79,6 +80,20 @@ impl TryFrom for UserId { )) } } + serde_json::Value::Object(mut m) => { + let param_count = m.len(); + if param_count > 1 { + Err(format!( + "Unexpected json format. Expected a single parameter. Received: {}", + param_count + )) + } else { + UserId::try_from(json!(m + .remove("user_id") + .or_else(|| m.remove("tower_id")) + .ok_or("user_id or tower_id not found")?)) + } + } _ => Err(format!( "Unexpected request format. Expected: user_id/tower_id. Received: '{}'", value @@ -86,3 +101,90 @@ impl TryFrom for UserId { } } } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use std::collections::HashMap; + + use crate::test_utils::get_random_user_id; + + #[test] + fn try_from_json_string() { + let user_id = get_random_user_id(); + assert_eq!(UserId::try_from(json!(user_id.to_string())), Ok(user_id)); + } + + #[test] + fn try_from_json_wrong_string() { + let user_id = "not_a_user_id"; + assert!(matches!( + UserId::try_from(json!(user_id.to_string())), + Err(..) + )); + } + + #[test] + fn try_from_json_array() { + let user_id = get_random_user_id(); + assert_eq!(UserId::try_from(json!([user_id.to_string()])), Ok(user_id)); + } + + #[test] + fn try_from_json_array_empty() { + assert!(matches!(UserId::try_from(json!([])), Err(..))); + } + + #[test] + fn try_from_json_array_too_many_elements() { + let user_id = get_random_user_id(); + assert!(matches!( + UserId::try_from(json!([user_id.to_string(), user_id.to_string()])), + Err(..) + )); + } + + #[test] + fn try_from_json_dict() { + let user_id = get_random_user_id(); + assert_eq!( + UserId::try_from(json!(HashMap::from([("tower_id", user_id.to_string())]))), + Ok(user_id) + ); + assert_eq!( + UserId::try_from(json!(HashMap::from([("user_id", user_id.to_string())]))), + Ok(user_id) + ); + } + + #[test] + fn try_from_json_empty_dict() { + assert!(matches!( + UserId::try_from(json!(HashMap::::new())), + Err(..) + )); + } + + #[test] + fn try_from_json_wrong_dict() { + let user_id = get_random_user_id(); + assert!(matches!( + UserId::try_from(json!(HashMap::from([("random_key", user_id.to_string())]))), + Err(..) + )); + } + + #[test] + fn try_from_json_dict_too_many_keys() { + let user_id = get_random_user_id(); + + assert!(matches!( + UserId::try_from(json!(HashMap::from([ + ("tower_id", user_id.to_string()), + ("user_id", user_id.to_string()) + ]))), + Err(..) + )); + } +} diff --git a/watchtower-plugin/src/convert.rs b/watchtower-plugin/src/convert.rs index 41ef4b2..db86447 100644 --- a/watchtower-plugin/src/convert.rs +++ b/watchtower-plugin/src/convert.rs @@ -3,6 +3,7 @@ use std::{convert::TryFrom, str::FromStr}; use hex::FromHex; use serde::{Deserialize, Serialize}; +use serde_json::json; use bitcoin::{Transaction, Txid}; @@ -120,33 +121,44 @@ impl TryFrom for RegisterParams { }, serde_json::Value::Array(mut a) => { let param_count = a.len(); + match param_count { 1 => RegisterParams::try_from(a.pop().unwrap()), 2 | 3 => { - let tower_id = a.get(0).unwrap(); - let host = a.get(1).unwrap(); - - if !tower_id.is_string() { - return Err(RegisterError::InvalidId(format!("tower_id must be a string. Received: {}", tower_id))); - } - if !host.is_string() { - return Err(RegisterError::InvalidHost(format!("host must be a string. Received: {}", host))); - } - let port = if param_count == 3 { - let p = a.get(2).unwrap(); - if !p.is_u64() { - return Err(RegisterError::InvalidPort(format!("port must be a number. Received: {}", p))); - } - p.as_u64() - } else{ + let tower_id = a.get(0).unwrap().as_str().ok_or_else(|| RegisterError::InvalidId("tower_id must be a string".to_string()))?; + let host = Some(a.get(1).unwrap().as_str().ok_or_else(|| RegisterError::InvalidHost("host must be a string".to_string()))?); + let port = if let Some(p) = a.get(2) { + Some(p.as_u64().ok_or_else(|| RegisterError::InvalidPort(format!("port must be a number. Received: {}", p)))?) + } else { None }; - RegisterParams::new(tower_id.as_str().unwrap(), host.as_str(), port) + RegisterParams::new(tower_id, host, port) } _ => Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {}", param_count))), } }, + serde_json::Value::Object(mut m) => { + let allowed_keys = ["tower_id", "host", "port"]; + let param_count = m.len(); + + if m.is_empty() || param_count > allowed_keys.len() { + Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {}", param_count))) + } else if !m.contains_key(allowed_keys[0]){ + Err(RegisterError::InvalidId(format!("{} is mandatory", allowed_keys[0]))) + } else if !m.iter().all(|(k, _)| allowed_keys.contains(&k.as_str())) { + Err(RegisterError::InvalidFormat("Invalid named parameter found in request".to_owned())) + } else { + let mut params = Vec::with_capacity(allowed_keys.len()); + for k in allowed_keys { + if let Some(v) = m.remove(k) { + params.push(v); + } + } + + RegisterParams::try_from(json!(params)) + } + }, _ => Err(RegisterError::InvalidFormat( format!("Unexpected request format. Expected: 'tower_id[@host][:port]' or 'tower_id [host] [port]'. Received: '{}'", value), )), @@ -215,6 +227,33 @@ impl TryFrom for GetAppointmentParams { Ok(Self { tower_id, locator }) } } + serde_json::Value::Object(mut m) => { + let allowed_keys = ["tower_id", "locator"]; + + if m.len() > allowed_keys.len() { + return Err(GetAppointmentError::InvalidFormat( + "Invalid named argument found in request".to_owned(), + )); + } + + // DISCUSS: There may be a more idiomatic way of doing this + for k in allowed_keys.iter() { + if !m.contains_key(*k) { + return Err(GetAppointmentError::InvalidFormat(format!( + "{} is mandatory", + k + ))); + } + } + + let mut params = Vec::with_capacity(allowed_keys.len()); + for k in allowed_keys { + if let Some(v) = m.remove(k) { + params.push(v); + } + } + GetAppointmentParams::try_from(json!(params)) + } _ => Err(GetAppointmentError::InvalidFormat(format!( "Unexpected request format. Expected: tower_id locator. Received: '{}'", value @@ -238,6 +277,7 @@ pub struct CommitmentRevocation { mod tests { use super::*; use serde_json::json; + use std::collections::HashMap; const VALID_ID: &str = "020dea894c967319407265764aba31bdef75d463f96800f34dd6df61380d82dfc0"; @@ -372,6 +412,61 @@ mod tests { assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); } + #[test] + fn test_try_from_json_dict() { + let id = json!(VALID_ID); + let host = json!("host"); + let port = json!(80); + + for v in [ + HashMap::from([("tower_id", &id), ("host", &host), ("port", &port)]), + HashMap::from([("tower_id", &id), ("host", &host)]), + HashMap::from([("tower_id", &id)]), + ] { + let p = RegisterParams::try_from(json!(v)); + assert!(matches!(p, Ok(..))); + } + + // Id key missing + let p = + RegisterParams::try_from(json!(HashMap::from([("host", &host), ("port", &port)]))); + assert!(matches!(p, Err(RegisterError::InvalidId(..)))); + + // Wrong id key + let p = RegisterParams::try_from(json!(HashMap::from([ + ("wrong_tower_id", &id), + ("tower_id", &id), + ("host", &host), + ("port", &port) + ]))); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + + // Wrong host key + let p = RegisterParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("wrong_host", &host), + ("port", &port) + ]))); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + + // Wrong port key + let p = RegisterParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("host", &host), + ("wrong_port", &port) + ]))); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + + // Wrong param count (params should be 1-3) + let p = RegisterParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("host", &host), + ("port", &port), + ("another_param", &json!(0)) + ]))); + assert!(matches!(p, Err(RegisterError::InvalidFormat(..)))); + } + #[test] fn test_try_from_other_json() { // Unexpected json object (it must be either String or Array) @@ -415,6 +510,40 @@ mod tests { assert!(matches!(p, Err(GetAppointmentError::InvalidLocator(..)))); } + #[test] + fn test_try_from_dict() { + let id = json!(VALID_ID); + let locator = json!("c69517f00d9482e6b1c41639f9bdfd5c"); + + // Valid params + let p = GetAppointmentParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("locator", &locator) + ]))); + assert!(matches!(p, Ok(..))); + + // Wrong keys + let p = GetAppointmentParams::try_from(json!(HashMap::from([ + ("wrong_tower_id", &id), + ("locator", &locator) + ]))); + assert!(matches!(p, Err(GetAppointmentError::InvalidFormat(..)))); + + let p = GetAppointmentParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("wrong_locator", &locator) + ]))); + assert!(matches!(p, Err(GetAppointmentError::InvalidFormat(..)))); + + // Too many parameters + let p = GetAppointmentParams::try_from(json!(HashMap::from([ + ("tower_id", &id), + ("locator", &locator), + ("another_param", &json!(0)) + ]))); + assert!(matches!(p, Err(GetAppointmentError::InvalidFormat(..)))); + } + #[test] fn test_try_from_other_json() { // Unexpected json object (it must be either String or Array) diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 81b26cd..40e77b2 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -177,7 +177,7 @@ pub async fn post_request( }; client.post(endpoint).json(&data).send().await.map_err(|e| { - log::debug!("POST request failed: {:?}", e); + log::debug!("An error ocurred when sending data to the tower: {}", e); if e.is_connect() | e.is_timeout() { RequestError::ConnectionError( "Cannot connect to the tower. Connection refused".to_owned(), From 4a7a2e02284e11b4b64194ba79fce6350b93317d Mon Sep 17 00:00:00 2001 From: Richard Ulrich Date: Mon, 9 Jan 2023 12:35:23 +0100 Subject: [PATCH 061/119] using mainnet rather than bitcoin in the output of the help command --- teos/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/config.rs b/teos/src/config.rs index 01e2b75..70d9278 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -62,7 +62,7 @@ pub struct Opt { #[structopt(long)] pub rpc_port: Option, - /// Network bitcoind is connected to. Either bitcoin, testnet, signet or regtest [default: bitcoin] + /// Network bitcoind is connected to. Either mainnet, testnet, signet or regtest [default: mainnet] #[structopt(long)] pub btc_network: Option, From 07caee2dbda894db462768290af2c922f1e30124 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 19 Dec 2022 17:15:41 +0100 Subject: [PATCH 062/119] Revamps the cln-pugin to support retrying towers automatically - Updates `Retrier::run` to return more meaningful errors. `Retrier::run` used to simply return a message, revamps it to return RetryError variants so we can handle return cases better. - Adds an additional state to `RetrierStatus`: Idle. Retries that fail due to an accumulation of transient errors will be flagged as Idle instead of Failed and retried later on (based on `auto_retry_delay`). Notice Retrier data is not kept in memory while a retrier is Idle. Instead, data is cleared and loaded again from the database when the `Retrier` is ready to run again. - Revamps how revocation data is sent to the `RetryManager`: The RetrierManager used to received locators one by one via unreachable_towers. This is due to them being mainly fed by `on_commitment_revocation`, which generates them one by one. However, both when manually retrying or when bootstrapping from an already populated database, multiple appointments may be pending for the same tower, hence needing to call `unreachable_towers.send` multiple times for the same tower. This itself was not a big deal, given we didn't really needed to differentiate between the cases. We do now though. In order to implement periodic retries while allowing manual retries we need to be able to signal the state transition to the `Retrier` without providing any new data: - If a Retrier is idle and we receive data trough `on_commitment_revocation` we need to append that data to the `Retrier`. - If a Retrier is iddle and we receive data trough a manual retry, we need to change the state of the `Retrier` without adding any new data to it. In order to implement this we've added an additional map to `WTClient` that reports the state of the active retriers. Retriers are active only if they are running or idle. - Also reworks `WTClient::set_tower_status` to update the status only if the new one does not match the old one. This is simply to reduce the boiler plate of having to perform this check in other pats of the plugin codebase. --- watchtower-plugin/src/main.rs | 123 ++++--- watchtower-plugin/src/retrier.rs | 510 ++++++++++++++++++++++------- watchtower-plugin/src/wt_client.rs | 73 ++++- 3 files changed, 534 insertions(+), 172 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 204939f..ab89058 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use std::env; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use home::home_dir; use serde_json::json; @@ -21,7 +21,7 @@ use watchtower_plugin::net::http::{ self, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, }; use watchtower_plugin::retrier::RetryManager; -use watchtower_plugin::wt_client::WTClient; +use watchtower_plugin::wt_client::{RevocationData, WTClient}; use watchtower_plugin::TowerStatus; fn to_cln_error(e: RequestError) -> Error { @@ -34,6 +34,27 @@ fn to_cln_error(e: RequestError) -> Error { e } +/// Sends fresh data to a retrier as long as is does not exist, or it does and its running. +fn send_to_retrier(state: &MutexGuard, tower_id: TowerId, locator: Locator) { + if if let Some(status) = state.get_retrier_status(&tower_id) { + // A retrier in the retriers map can only be running or idle + status.is_running() + } else { + true + } { + state + .unreachable_towers + .send((tower_id, RevocationData::Fresh(locator))) + .unwrap(); + } else { + log::debug!( + "Not sending data to idle retrier ({}, {})", + tower_id, + locator + ) + } +} + /// Registers the client to a given tower. /// /// Accepted tower_id formats: @@ -286,38 +307,52 @@ async fn get_tower_info( /// Triggers a manual retry of a tower, tries to send all pending appointments to it. /// -/// Only works if the tower is unreachable or there's been a subscription error. +/// Only works if the tower is unreachable or there's been a subscription error (and the tower is not already being retried). async fn retry_tower( plugin: Plugin>>, v: serde_json::Value, ) -> Result { let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; let state = plugin.state().lock().unwrap(); - if let Some(status) = state.get_tower_status(&tower_id) { - if status.is_temporary_unreachable() { - return Err(anyhow!("{} is already being retried", tower_id)); - } else if !status.is_retryable() { + if let Some(tower_status) = state.get_tower_status(&tower_id) { + if let Some(retrier_status) = state.retriers.get(&tower_id) { + if retrier_status.is_idle() { + // We don't send any associated data in this case given the idle retrier already has it all. + state + .unreachable_towers + .send((tower_id, RevocationData::None)) + .map_err(|e| anyhow!(e))?; + } else { + // Status can only be running or idle for data in the retriers map. + return Err(anyhow!("{} is already being retried", tower_id)); + } + } else if tower_status.is_retryable() { + // We do send associated data here given there is no retrier associated to this tower. + state + .unreachable_towers + .send(( + tower_id, + RevocationData::Stale( + state + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .iter() + .cloned() + .collect(), + ), + )) + .map_err(|e| anyhow!(e))?; + } else { return Err(anyhow!( "Tower status must be unreachable or have a subscription issue to manually retry", )); } - - for locator in state - .towers - .get(&tower_id) - .unwrap() - .pending_appointments - .iter() - { - state - .unreachable_towers - .send((tower_id, *locator)) - .map_err(|e| anyhow!(e))?; - } - Ok(json!(format!("Retrying {}", tower_id))) } else { - Err(anyhow!("Unknown tower {}", tower_id)) + return Err(anyhow!("Unknown tower {}", tower_id)); } + Ok(json!(format!("Retrying {}", tower_id))) } /// Forgets about a tower wiping out all local data associated to it. @@ -410,11 +445,7 @@ async fn on_commitment_revocation( let mut state = plugin.state().lock().unwrap(); state.set_tower_status(tower_id, TowerStatus::TemporaryUnreachable); state.add_pending_appointment(tower_id, &appointment); - - state - .unreachable_towers - .send((tower_id, appointment.locator)) - .unwrap(); + send_to_retrier(&state, tower_id, appointment.locator); } } AddAppointmentError::ApiError(e) => match e.error_code { @@ -427,11 +458,7 @@ async fn on_commitment_revocation( let mut state = plugin.state().lock().unwrap(); state.set_tower_status(tower_id, TowerStatus::SubscriptionError); state.add_pending_appointment(tower_id, &appointment); - - state - .unreachable_towers - .send((tower_id, appointment.locator)) - .unwrap(); + send_to_retrier(&state, tower_id, appointment.locator); } _ => { @@ -482,11 +509,8 @@ async fn on_commitment_revocation( let mut state = plugin.state().lock().unwrap(); state.add_pending_appointment(tower_id, &appointment); - if status.is_temporary_unreachable() { - state - .unreachable_towers - .send((tower_id, appointment.locator)) - .unwrap(); + if !status.is_unreachable() { + send_to_retrier(&state, tower_id, appointment.locator); } } } @@ -517,7 +541,11 @@ async fn main() -> Result<(), Error> { "watchtower-proxy", Value::OptString, "Socks v5 proxy IP address and port for the watchtower client", - )) + )).option(ConfigOption::new( + "watchtower-auto-retry-delay", + Value::Integer(86400), + "the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day", + )) .option(ConfigOption::new( "dev-watchtower-max-retry-interval", Value::Integer(60), @@ -593,6 +621,13 @@ async fn main() -> Result<(), Error> { // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. 900 }; + let auto_retry_delay = + if let Value::Integer(x) = midstate.option("watchtower-auto-retry-delay").unwrap() { + x as u16 + } else { + // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. + 3600 + }; let max_interval_time = if let Value::Integer(x) = midstate .option("dev-watchtower-max-retry-interval") .unwrap() @@ -605,9 +640,15 @@ async fn main() -> Result<(), Error> { let plugin = midstate.start(wt_client.clone()).await?; tokio::spawn(async move { - RetryManager::new(wt_client, rx, max_elapsed_time, max_interval_time) - .manage_retry() - .await + RetryManager::new( + wt_client, + rx, + max_elapsed_time, + auto_retry_delay, + max_interval_time, + ) + .manage_retry() + .await }); plugin.join().await } diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 408ba61..64e579b 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use std::sync::{Arc, Mutex}; -use std::time::Duration; +use std::time::{Duration, Instant}; use tokio::sync::mpsc::{error::TryRecvError, UnboundedReceiver}; use backoff::future::retry_notify; @@ -12,12 +13,43 @@ use teos_common::errors; use teos_common::UserId as TowerId; use crate::net::http::{self, AddAppointmentError}; -use crate::wt_client::WTClient; +use crate::wt_client::{RevocationData, WTClient}; +use crate::{MisbehaviorProof, TowerStatus}; + +#[derive(Eq, PartialEq, Debug)] +enum RetryError { + // bool marks whether the Subscription error is permanent or not + Subscription(String, bool), + Unreachable, + Misbehaving(MisbehaviorProof), + Abandoned, +} + +impl Display for RetryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RetryError::Subscription(r, _) => write!(f, "{}", r), + RetryError::Unreachable => write!(f, "Tower cannot be reached"), + RetryError::Misbehaving(_) => write!(f, "Tower misbehaved"), + RetryError::Abandoned => write!(f, "Tower was abandoned. Skipping retry"), + } + } +} + +impl RetryError { + fn is_permanent(&self) -> bool { + matches!( + self, + RetryError::Subscription(_, true) | RetryError::Misbehaving(_) | RetryError::Abandoned + ) + } +} pub struct RetryManager { wt_client: Arc>, - unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, + unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>, max_elapsed_time_secs: u16, + auto_retry_delay: u16, max_interval_time_secs: u16, retriers: HashMap>, } @@ -25,14 +57,16 @@ pub struct RetryManager { impl RetryManager { pub fn new( wt_client: Arc>, - unreachable_towers: UnboundedReceiver<(TowerId, Locator)>, + unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>, max_elapsed_time_secs: u16, + auto_retry_delay: u16, max_interval_time_secs: u16, ) -> Self { RetryManager { wt_client, unreachable_towers, max_elapsed_time_secs, + auto_retry_delay, max_interval_time_secs, retriers: HashMap::new(), } @@ -41,15 +75,20 @@ impl RetryManager { /// Starts the retry manager's main logic loop. /// This method will keep running until the `unreachable_towers` sender disconnects. /// - /// It will receive any `(tower_id, locator)` pair and try to send the appointment with `locator` to - /// the tower with `tower_id`. This is done by spawning a tokio thread for each `tower_id` that tries - /// to send all the pending appointments. + /// It will receive a `(tower_id, revocation_data)` pair and try to send all the appointments contained + /// in `revocation_data` (identified by `locator`) to the tower with `tower_id`. This is done by spawning + /// a tokio thread for each `tower_id` that tries to send all the pending appointments. + /// + /// The content of [RevocationData] will depend on who called `unreachable_towers.send`: + /// - If it was called by `on_commitment_revocation`, the data will be fresh and contain a single locator + /// - If it was called by the [WTClient] constructor, or by manually retrying, then the data will the stale + /// and contain a `HashSet` with, potentially, many locators. pub async fn manage_retry(&mut self) { log::info!("Starting retry manager"); loop { match self.unreachable_towers.try_recv() { - Ok((tower_id, locator)) => { + Ok((tower_id, data)) => { // Not start a retry if the tower is flagged to be abandoned if !self .wt_client @@ -59,9 +98,35 @@ impl RetryManager { .contains_key(&tower_id) { log::info!("Skipping retrying abandoned tower {}", tower_id); - continue; + } else if let Some(retrier) = self.retriers.get(&tower_id) { + if retrier.is_idle() { + if !data.is_none() { + log::error!("Data was send to an idle retier. This should have never happened. Please report! ({:?})", data); + continue; + } + log::info!( + "Manually finished idling. Flagging {} for retry", + retrier.tower_id + ); + // While a retrier is idle data is not kept in memory. + // Load the pending appointments from the DB and feed them to the retrier + retrier.set_status(RetrierStatus::Stopped); + retrier.pending_appointments.lock().unwrap().extend( + self.wt_client + .lock() + .unwrap() + .dbm + .load_appointment_locators( + retrier.tower_id, + crate::AppointmentStatus::Pending, + ), + ); + } else { + self.add_pending_appointments(tower_id, data.into()); + } + } else { + self.add_pending_appointments(tower_id, data.into()); } - self.add_pending_appointment(tower_id, locator); } Err(TryRecvError::Empty) => { // Keep only running retriers and retriers ready to be started/re-started. @@ -71,13 +136,34 @@ impl RetryManager { // them because we know that that tower is unreachable. We most likely received these new appointments while the tower // was still flagged as temporarily unreachable when cleaning up after giving up retrying. self.retriers.retain(|_, retrier| { - retrier.set_tower_status_if_failed(); - retrier.is_running() || retrier.should_start() + retrier.remove_if_failed(); + retrier.should_start() || retrier.is_running() || retrier.is_idle() }); // Start all the ready retriers. for retrier in self.retriers.values() { if retrier.should_start() { self.start_retrying(retrier.clone()); + // Effectively this is the same as `if retrier.is_idle` plus returning for how long is true. + } else if let Some(t) = retrier.get_elapsed_time() { + if t > self.auto_retry_delay as u64 { + log::info!( + "Finished idling. Flagging {} for retry", + retrier.tower_id + ); + // While a retrier is idle data is not kept in memory. + // Load the pending appointments from the DB and feed them to the retrier + retrier.set_status(RetrierStatus::Stopped); + retrier.pending_appointments.lock().unwrap().extend( + self.wt_client + .lock() + .unwrap() + .dbm + .load_appointment_locators( + retrier.tower_id, + crate::AppointmentStatus::Pending, + ), + ); + } } } // Sleep to not waste a lot of CPU cycles. @@ -91,31 +177,30 @@ impl RetryManager { /// Adds an appointment to pending for a given tower. /// /// If the tower is not currently being retried, a new entry for it is created, otherwise, the data is appended to the existing entry. - fn add_pending_appointment(&mut self, tower_id: TowerId, locator: Locator) { + fn add_pending_appointments(&mut self, tower_id: TowerId, locators: HashSet) { if let std::collections::hash_map::Entry::Vacant(e) = self.retriers.entry(tower_id) { - log::debug!( - "Creating a new entry for tower {} with locator {}", - tower_id, - locator - ); + log::debug!("Creating a new entry for tower {} ", tower_id); e.insert(Arc::new(Retrier::new( self.wt_client.clone(), tower_id, - locator, + locators, ))); } else { - log::debug!( - "Adding pending appointment {} to existing tower {}", - locator, - tower_id - ); - self.retriers + let mut pending_appointments = self + .retriers .get(&tower_id) .unwrap() .pending_appointments .lock() - .unwrap() - .insert(locator); + .unwrap(); + for locator in locators { + log::debug!( + "Adding pending appointment {} to existing tower {}", + locator, + tower_id + ); + pending_appointments.insert(locator); + } } } @@ -125,7 +210,7 @@ impl RetryManager { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum RetrierStatus { /// Retrier is stopped. This could happen if the retrier was never started or it started and /// finished successfully. If a retrier is stopped and has some pending appointments, it should be @@ -140,6 +225,42 @@ pub enum RetrierStatus { /// /// If a retrier status is `Failed`, then its associated tower is neither reachable nor temporary unreachable. Failed, + /// Retrier is currently idle waiting for a signal to start working again. An Idle retrier can be forced to start + /// working again by the user by manually calling `retrytower`. + /// + /// If a retrier status is `Idle`, then its associated tower is unreachable. + Idle(Instant), +} + +impl RetrierStatus { + /// Check whether the status is [Running](RetrierStatus::Stopped). + pub fn is_stopped(&self) -> bool { + *self == RetrierStatus::Stopped + } + + /// Check whether the status is [Running](RetrierStatus::Running). + pub fn is_running(&self) -> bool { + *self == RetrierStatus::Running + } + + /// Check whether the status is [Idle](RetrierStatus::Idle). + pub fn is_idle(&self) -> bool { + matches!(self, RetrierStatus::Idle { .. }) + } + + /// Check whether the status is [Failed](RetrierStatus::Failed). + pub fn failed(&self) -> bool { + *self == RetrierStatus::Failed + } + + /// Gets the elapsed time of an [Idle](RetrierStatus::Idle) status, [None] otherwise. + pub fn get_elapsed_time(&self) -> Option { + if let RetrierStatus::Idle(x) = *self { + Some(x.elapsed().as_secs()) + } else { + None + } + } } pub struct Retrier { @@ -150,11 +271,15 @@ pub struct Retrier { } impl Retrier { - pub fn new(wt_client: Arc>, tower_id: TowerId, locator: Locator) -> Self { + pub fn new( + wt_client: Arc>, + tower_id: TowerId, + locators: HashSet, + ) -> Self { Self { wt_client, tower_id, - pending_appointments: Mutex::new(HashSet::from([locator])), + pending_appointments: Mutex::new(locators), status: Mutex::new(RetrierStatus::Stopped), } } @@ -164,17 +289,57 @@ impl Retrier { } fn set_status(&self, status: RetrierStatus) { - *self.status.lock().unwrap() = status; + *self.status.lock().unwrap() = status.clone(); + + // Add or remove retriers from WTClient based on the RetrierStatus + if self.is_running() || self.is_idle() { + log::debug!("Adding {} to active retriers", self.tower_id); + self.wt_client + .lock() + .unwrap() + .retriers + .insert(self.tower_id, status); + } else if self.is_stopped() { + // We are not removing failed retriers here to prevent a manual retry until the retrier is removed from + // the manager + log::debug!("Removing retrier {} from active retriers", self.tower_id); + self.wt_client + .lock() + .unwrap() + .retriers + .remove(&self.tower_id); + } } + /// Maps [RetrierStatus::is_stopped] + pub fn is_stopped(&self) -> bool { + self.status.lock().unwrap().is_stopped() + } + + /// Maps [RetrierStatus::is_running] pub fn is_running(&self) -> bool { - *self.status.lock().unwrap() == RetrierStatus::Running + self.status.lock().unwrap().is_running() + } + + /// Maps [RetrierStatus::is_idle] + pub fn is_idle(&self) -> bool { + self.status.lock().unwrap().is_idle() + } + + /// Maps [RetrierStatus::failed] + pub fn failed(&self) -> bool { + self.status.lock().unwrap().failed() + } + + /// Maps [RetrierStatus::get_elapsed_time] + pub fn get_elapsed_time(&self) -> Option { + self.status.lock().unwrap().get_elapsed_time() } pub fn should_start(&self) -> bool { // A retrier can be started/re-started if it is stopped (i.e. not running and not failed) // and has some pending appointments. - *self.status.lock().unwrap() == RetrierStatus::Stopped && self.has_pending_appointments() + self.is_stopped() && self.has_pending_appointments() } pub fn start(self: Arc, max_elapsed_time_secs: u16, max_interval_time_secs: u16) { @@ -192,10 +357,10 @@ impl Retrier { .unwrap() .is_subscription_error() { - state.set_tower_status(self.tower_id, crate::TowerStatus::TemporaryUnreachable); + state.set_tower_status(self.tower_id, TowerStatus::TemporaryUnreachable); } - self.set_status(RetrierStatus::Running); } + self.set_status(RetrierStatus::Running); tokio::spawn(async move { let r = retry_notify( @@ -211,13 +376,14 @@ impl Retrier { ) .await; - let mut state = self.wt_client.lock().unwrap(); - match r { Ok(_) => { log::info!("Retry strategy succeeded for {}", self.tower_id); // Set the tower status now so new appointment doesn't go to the retry manager. - state.set_tower_status(self.tower_id, crate::TowerStatus::Reachable); + self.wt_client + .lock() + .unwrap() + .set_tower_status(self.tower_id, TowerStatus::Reachable); // Retrier succeeded and can be re-used by re-starting it. self.set_status(RetrierStatus::Stopped); } @@ -225,23 +391,51 @@ impl Retrier { // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior). log::warn!("Retry strategy gave up for {}. {}", self.tower_id, e); + if e.is_permanent() { + self.set_status(RetrierStatus::Failed); + } - // Retrier failed and should be given up on. Avoid setting the tower status until the retrier is - // deleted/dropped. This way users performing manual retry will get an error as the tower will be - // temporary unreachable. - // We don't need to set the tower status now. Any new appointments we receive will not be retried anyways. - self.set_status(RetrierStatus::Failed); + match e { + RetryError::Subscription(_, true) => { + log::info!("Setting {} status as subscription error", self.tower_id); + self.wt_client + .lock() + .unwrap() + .set_tower_status(self.tower_id, TowerStatus::SubscriptionError) + } + RetryError::Misbehaving(p) => { + log::warn!("Cannot recover known tower_id from the appointment receipt. Flagging tower as misbehaving"); + self.wt_client + .lock() + .unwrap() + .flag_misbehaving_tower(self.tower_id, p); + } + RetryError::Abandoned => { + log::info!("Skipping retrying abandoned tower {}", self.tower_id) + } + // This covers `RetryError::Unreachable` and `RetryError::Subscription(_, false)` + _ => { + log::debug!("Starting to idle"); + self.set_status(RetrierStatus::Idle(Instant::now())); + // Clear all pending appointments so they do not waste any memory while idling + self.pending_appointments.lock().unwrap().clear(); + self.wt_client + .lock() + .unwrap() + .set_tower_status(self.tower_id, TowerStatus::Unreachable); + } + } } } }); } - async fn run(&self) -> Result<(), Error<&'static str>> { + async fn run(&self) -> Result<(), Error> { // Create a new scope so we can get all the data only locking the WTClient once. let (tower_id, status, net_addr, user_id, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); if wt_client.towers.get(&self.tower_id).is_none() { - return Err(Error::permanent("Tower was abandoned. Skipping retry")); + return Err(Error::permanent(RetryError::Abandoned)); } let tower = wt_client.towers.get(&self.tower_id).unwrap(); @@ -261,23 +455,26 @@ impl Retrier { .await .map_err(|e| { log::debug!("Cannot renew registration with tower. Error: {:?}", e); - Error::permanent("Cannot renew registration with tower") + Error::transient(RetryError::Subscription( + "Cannot renew registration with tower".to_owned(), + false, + )) })?; if !receipt.verify(&tower_id) { - return Err(Error::permanent( - "Registration receipt contains bad signature. Are you using the right tower_id?" - )); + return Err(Error::permanent(RetryError::Subscription("Registration receipt contains bad signature. Are you using the right tower_id?".to_owned(), true))); } self.wt_client - .lock() - .unwrap() - .add_update_tower(tower_id, &net_addr, &receipt).map_err(|e| { - if e.is_expiry() { - Error::permanent("Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for") - } else { - Error::permanent("Registration receipt does not contain more slots than the ones we are currently registered for") - } - })?; + .lock() + .unwrap() + .add_update_tower(tower_id, &net_addr, &receipt) + .map_err(|e| { + let reason = if e.is_expiry() { + "Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for" + } else { + "Registration receipt does not contain more slots than the ones we are currently registered for" + }; + Error::permanent(RetryError::Subscription(reason.to_owned(), true)) + })?; } while self.has_pending_appointments() { @@ -320,17 +517,20 @@ impl Retrier { "{} cannot be reached. Tower will be retried later", tower_id, ); - return Err(Error::transient("Tower cannot be reached")); + return Err(Error::transient(RetryError::Unreachable)); } } AddAppointmentError::ApiError(e) => match e.error_code { errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { log::warn!("There is a subscription issue with {}", tower_id); - self.wt_client.lock().unwrap().set_tower_status( - tower_id, - crate::TowerStatus::SubscriptionError, - ); - return Err(Error::transient("Subscription error")); + self.wt_client + .lock() + .unwrap() + .set_tower_status(tower_id, TowerStatus::SubscriptionError); + return Err(Error::transient(RetryError::Subscription( + "Subscription error".to_owned(), + false, + ))); } _ => { log::warn!( @@ -349,12 +549,7 @@ impl Retrier { } }, AddAppointmentError::SignatureError(proof) => { - log::warn!("Cannot recover known tower_id from the appointment receipt. Flagging tower as misbehaving"); - self.wt_client - .lock() - .unwrap() - .flag_misbehaving_tower(tower_id, proof); - return Err(Error::permanent("Tower misbehaved")); + return Err(Error::permanent(RetryError::Misbehaving(proof))); } } } @@ -365,21 +560,18 @@ impl Retrier { Ok(()) } - /// Sets the correct tower status if the retrier status is failed. - /// - /// This method MUST be called before getting rid of a failed retrier, and has - /// no effect on non-failed retriers. - pub fn set_tower_status_if_failed(&self) { - if *self.status.lock().unwrap() == RetrierStatus::Failed { - let mut state = self.wt_client.lock().unwrap(); - if let Some(status) = state.get_tower_status(&self.tower_id) { - if status.is_temporary_unreachable() { - log::warn!("Setting {} as unreachable", self.tower_id); - state.set_tower_status(self.tower_id, crate::TowerStatus::Unreachable); - } - } else { - log::info!("Skipping retrying abandoned tower {}", self.tower_id); - } + /// Removed our retrier identifier from the WTClient if the retrier has failed + pub fn remove_if_failed(&self) { + if self.failed() { + log::debug!( + "Removing failed retrier {} from active retriers", + self.tower_id + ); + self.wt_client + .lock() + .unwrap() + .retriers + .remove(&self.tower_id); } } } @@ -390,6 +582,7 @@ mod tests { use httpmock::prelude::*; use serde_json::json; + use std::iter::FromIterator; use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; @@ -402,9 +595,9 @@ mod tests { use crate::net::http::ApiError; use crate::test_utils::get_dummy_add_appointment_response; - use crate::TowerStatus; const MAX_ELAPSED_TIME: u16 = 2; + const AUTO_RETRY_TIME: u16 = 5; const MAX_INTERVAL_TIME: u16 = 1; impl Retrier { @@ -464,11 +657,21 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); - tx.send((tower_id, appointment.locator)).unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([appointment.locator])), + )) + .unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -524,11 +727,21 @@ mod tests { let max_elapsed_time = MAX_ELAPSED_TIME + 1; let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); - tx.send((tower_id, appointment.locator)).unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([appointment.locator])), + )) + .unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(max_elapsed_time as u64 / 3)).await; @@ -591,11 +804,21 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); - tx.send((tower_id, appointment.locator)).unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([appointment.locator])), + )) + .unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -673,11 +896,21 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); - tx.send((tower_id, appointment.locator)).unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([appointment.locator])), + )) + .unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -717,14 +950,25 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); // Send the id and check how it gets removed - tx.send((tower_id, generate_random_appointment(None).locator)) - .unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([ + generate_random_appointment(None).locator + ])), + )) + .unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); @@ -794,11 +1038,21 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { - RetryManager::new(wt_client_clone, rx, MAX_ELAPSED_TIME, MAX_INTERVAL_TIME) - .manage_retry() - .await + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + AUTO_RETRY_TIME, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await }); - tx.send((tower_id, appointment.locator)).unwrap(); + tx.send(( + tower_id, + RevocationData::Stale(HashSet::from_iter([appointment.locator])), + )) + .unwrap(); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; @@ -853,7 +1107,7 @@ mod tests { }); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; assert_eq!(r, Ok(())); api_mock.assert(); @@ -923,9 +1177,12 @@ mod tests { }); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; - assert_eq!(r, Err(Error::permanent("Tower misbehaved"))); + assert!(matches!( + r, + Err(Error::Permanent(RetryError::Misbehaving { .. },)) + )); api_mock.assert(); } @@ -954,10 +1211,10 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; - assert_eq!(r, Err(Error::transient("Tower cannot be reached"))); + assert_eq!(r, Err(Error::transient(RetryError::Unreachable))); } #[tokio::test] @@ -996,10 +1253,16 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client, tower_id, appointment.locator); + let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; - assert_eq!(r, Err(Error::transient("Subscription error"))); + assert!(matches!( + r, + Err(Error::Transient { + err: RetryError::Subscription { .. }, + .. + }) + )); api_mock.assert(); } @@ -1039,7 +1302,11 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Since we are retrying manually, we need to add the data to pending appointments manually too - let retrier = Retrier::new(wt_client.clone(), tower_id, appointment.locator); + let retrier = Retrier::new( + wt_client.clone(), + tower_id, + HashSet::from([appointment.locator]), + ); let r = retrier.run().await; assert_eq!(r, Ok(())); @@ -1078,9 +1345,6 @@ mod tests { // If there are no pending appointments the method will simply return let r = Retrier::empty(wt_client, tower_id).run().await; - assert_eq!( - r, - Err(Error::permanent("Tower was abandoned. Skipping retry")) - ); + assert_eq!(r, Err(Error::permanent(RetryError::Abandoned))); } } diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 175182f..9811543 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -1,4 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; use std::path::PathBuf; use tokio::fs; use tokio::sync::mpsc::UnboundedSender; @@ -12,16 +13,59 @@ use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::dbm::DBM; +use crate::retrier::RetrierStatus; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; +#[derive(Eq, PartialEq)] +pub enum RevocationData { + Fresh(Locator), + Stale(HashSet), + None, +} + +impl RevocationData { + pub fn is_none(&self) -> bool { + *self == RevocationData::None + } +} + +impl From for HashSet { + fn from(r: RevocationData) -> Self { + match r { + RevocationData::Fresh(l) => HashSet::from_iter(vec![l]), + RevocationData::Stale(hs) => hs, + RevocationData::None => HashSet::new(), + } + } +} + +impl std::fmt::Debug for RevocationData { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + RevocationData::Fresh(l) => format!("Fresh: {}", l), + RevocationData::Stale(hs) => format!( + "Stale: {:?}", + hs.iter().map(|l| l.to_string()).collect::>() + ), + RevocationData::None => "None".to_owned(), + } + ) + } +} + /// Represents the watchtower client that is being used as the CoreLN plugin state. pub struct WTClient { /// A [DBM] instance. pub dbm: DBM, /// A collection of towers the client is registered to. pub towers: HashMap, - /// Queue of unreachable towers - pub unreachable_towers: UnboundedSender<(TowerId, Locator)>, + /// Queue of unreachable towers. + pub unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, + // Map of existing retriers and its state. + pub retriers: HashMap, /// The user secret key. pub user_sk: SecretKey, /// The user identifier. @@ -33,7 +77,7 @@ pub struct WTClient { impl WTClient { pub async fn new( data_dir: PathBuf, - unreachable_towers: UnboundedSender<(TowerId, Locator)>, + unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, ) -> Self { // Create data dir if it does not exist fs::create_dir_all(&data_dir).await.unwrap_or_else(|e| { @@ -58,9 +102,12 @@ impl WTClient { let towers = dbm.load_towers(); for (tower_id, tower) in towers.iter() { if tower.status.is_temporary_unreachable() { - for locator in tower.pending_appointments.iter() { - unreachable_towers.send((*tower_id, *locator)).unwrap(); - } + unreachable_towers + .send(( + *tower_id, + RevocationData::Stale(tower.pending_appointments.iter().cloned().collect()), + )) + .unwrap(); } } @@ -72,6 +119,7 @@ impl WTClient { WTClient { towers, unreachable_towers, + retriers: HashMap::new(), dbm, user_sk, user_id, @@ -136,7 +184,11 @@ impl WTClient { /// Sets the tower status to any of the `TowerStatus` variants. pub fn set_tower_status(&mut self, tower_id: TowerId, status: TowerStatus) { if let Some(tower) = self.towers.get_mut(&tower_id) { - tower.status = status + if tower.status != status { + tower.status = status + } else { + log::debug!("{} status is already {}", tower_id, status) + } } else { log::error!( "Cannot change tower status to {}. Unknown tower_id: {}", @@ -146,6 +198,11 @@ impl WTClient { } } + /// Gets the given tower status (identified by tower_id), if found. + pub fn get_retrier_status(&self, tower_id: &TowerId) -> Option<&RetrierStatus> { + self.retriers.get(tower_id) + } + /// Adds an appointment receipt to the tower record. pub fn add_appointment_receipt( &mut self, From cc7acf6201d568c573f516240d3937e55d9ee369 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 21 Dec 2022 15:33:04 +0100 Subject: [PATCH 063/119] Adds watchtower-plugin autoretry tests --- watchtower-plugin/src/retrier.rs | 349 +++++++++++++++++++++++++------ watchtower-plugin/tests/test.py | 35 +++- 2 files changed, 315 insertions(+), 69 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 64e579b..f3c0173 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -582,7 +582,6 @@ mod tests { use httpmock::prelude::*; use serde_json::json; - use std::iter::FromIterator; use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; @@ -596,8 +595,10 @@ mod tests { use crate::net::http::ApiError; use crate::test_utils::get_dummy_add_appointment_response; + const LONG_AUTO_RETRY_DELAY: u16 = 60; + const SHORT_AUTO_RETRY_DELAY: u16 = 3; + const API_DELAY: f64 = 0.5; const MAX_ELAPSED_TIME: u16 = 2; - const AUTO_RETRY_TIME: u16 = 5; const MAX_INTERVAL_TIME: u16 = 1; impl Retrier { @@ -650,6 +651,7 @@ mod tests { let api_mock = server.mock(|when, then| { when.method(POST).path("/add_appointment"); then.status(200) + .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); }); @@ -661,31 +663,32 @@ mod tests { wt_client_clone, rx, MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, + LONG_AUTO_RETRY_DELAY, MAX_INTERVAL_TIME, ) .manage_retry() .await }); - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([appointment.locator])), - )) - .unwrap(); + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; - assert_eq!( - wt_client - .lock() - .unwrap() - .get_tower_status(&tower_id) - .unwrap(), - TowerStatus::Reachable - ); - assert!(!wt_client + tokio::time::sleep(Duration::from_secs((API_DELAY / 2.0) as u64)).await; + assert!(wt_client .lock() .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_running()); + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + + let state = wt_client.lock().unwrap(); + assert_eq!( + state.get_tower_status(&tower_id).unwrap(), + TowerStatus::Reachable + ); + assert!(!state.retriers.contains_key(&tower_id)); + assert!(!state .towers .get(&tower_id) .unwrap() @@ -706,7 +709,7 @@ mod tests { )); // Add a tower with pending appointments - let (_, tower_pk) = cryptography::get_random_keypair(); + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); let receipt = get_random_registration_receipt(); wt_client @@ -725,41 +728,101 @@ mod tests { // Start the task and send the tower to the channel for retry let wt_client_clone = wt_client.clone(); - let max_elapsed_time = MAX_ELAPSED_TIME + 1; - let task = tokio::spawn(async move { - RetryManager::new( - wt_client_clone, - rx, - MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, - MAX_INTERVAL_TIME, - ) - .manage_retry() - .await - }); - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([appointment.locator])), - )) - .unwrap(); + let mut retry_manager = RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME + 1, + SHORT_AUTO_RETRY_DELAY, + MAX_INTERVAL_TIME, + ); + let task = tokio::spawn(async move { retry_manager.manage_retry().await }); + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs(max_elapsed_time as u64 / 3)).await; + tokio::time::sleep(Duration::from_secs_f64( + (MAX_ELAPSED_TIME as f64 + 1.0) / 2.0, + )) + .await; assert!(wt_client .lock() .unwrap() .get_tower_status(&tower_id) .unwrap() .is_temporary_unreachable()); + assert!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_running()); - // Wait until the task gives up and check again - tokio::time::sleep(Duration::from_secs(max_elapsed_time as u64)).await; + // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, + // so the retiers will be idle). + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; assert!(wt_client .lock() .unwrap() .get_tower_status(&tower_id) .unwrap() .is_unreachable()); + assert!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_idle()); + + // Add a proper server and check that the auto-retry works + // Prepare the mock response + let server = MockServer::start(); + let mut add_appointment_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + add_appointment_receipt.sign(&tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); + let api_mock = server.mock(|when, then| { + when.method(POST).path("/add_appointment"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!(add_appointment_response)); + }); + + // Update the tower details + wt_client + .lock() + .unwrap() + .add_update_tower( + tower_id, + &server.base_url(), + &get_registration_receipt_from_previous(&receipt), + ) + .unwrap(); + + // Wait and check. We wait twice the short retry delay because it can be the case that the first auto retry + // is performed while we are patching the mock. + tokio::time::sleep(Duration::from_secs((SHORT_AUTO_RETRY_DELAY * 2) as u64)).await; + assert_eq!( + wt_client + .lock() + .unwrap() + .get_tower_status(&tower_id) + .unwrap(), + TowerStatus::Reachable + ); + assert!(!wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + + api_mock.assert(); task.abort(); } @@ -794,6 +857,7 @@ mod tests { let api_mock = server.mock(|when, then| { when.method(POST).path("/add_appointment"); then.status(400) + .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(ApiError { error: "error_msg".to_owned(), @@ -808,19 +872,23 @@ mod tests { wt_client_clone, rx, MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, + LONG_AUTO_RETRY_DELAY, MAX_INTERVAL_TIME, ) .manage_retry() .await }); - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([appointment.locator])), - )) - .unwrap(); - + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); // Wait for the elapsed time and check how the tower status changed + tokio::time::sleep(Duration::from_secs((API_DELAY / 2.0) as u64)).await; + assert!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_running()); + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; assert_eq!( wt_client @@ -830,6 +898,7 @@ mod tests { .unwrap(), TowerStatus::Reachable ); + assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); assert!(!wt_client .lock() .unwrap() @@ -889,6 +958,7 @@ mod tests { let api_mock = server.mock(|when, then| { when.method(POST).path("/add_appointment"); then.status(200) + .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); }); @@ -900,19 +970,24 @@ mod tests { wt_client_clone, rx, MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, + LONG_AUTO_RETRY_DELAY, MAX_INTERVAL_TIME, ) .manage_retry() .await }); - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([appointment.locator])), - )) - .unwrap(); + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); // Wait for the elapsed time and check how the tower status changed + tokio::time::sleep(Duration::from_secs_f64(API_DELAY / 2.0)).await; + assert!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_running()); + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; assert!(wt_client .lock() @@ -920,6 +995,7 @@ mod tests { .get_tower_status(&tower_id) .unwrap() .is_misbehaving()); + assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); api_mock.assert(); task.abort(); @@ -954,21 +1030,15 @@ mod tests { wt_client_clone, rx, MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, + LONG_AUTO_RETRY_DELAY, MAX_INTERVAL_TIME, ) .manage_retry() .await }); - // Send the id and check how it gets removed - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([ - generate_random_appointment(None).locator - ])), - )) - .unwrap(); + // Send a retry request and check how the tower is removed + tx.send((tower_id, RevocationData::None)).unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); @@ -1014,6 +1084,7 @@ mod tests { let add_appointment_mock = server.mock(|when, then| { when.method(POST).path("/add_appointment"); then.status(200) + .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); }); @@ -1025,6 +1096,7 @@ mod tests { let register_mock = server.mock(|when, then| { when.method(POST).path("/register"); then.status(200) + .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(re_registration_receipt)); }); @@ -1042,21 +1114,28 @@ mod tests { wt_client_clone, rx, MAX_ELAPSED_TIME, - AUTO_RETRY_TIME, + LONG_AUTO_RETRY_DELAY, MAX_INTERVAL_TIME, ) .manage_retry() .await }); - tx.send(( - tower_id, - RevocationData::Stale(HashSet::from_iter([appointment.locator])), - )) - .unwrap(); + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); + + tokio::time::sleep(Duration::from_secs_f64(API_DELAY / 2.0)).await; + assert!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_running()); // Wait for the elapsed time and check how the tower status changed tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; let state = wt_client.lock().unwrap(); + assert!(!state.retriers.contains_key(&tower_id)); + let tower = state.towers.get(&tower_id).unwrap(); assert!(tower.status.is_reachable()); assert!(tower.pending_appointments.is_empty()); @@ -1066,6 +1145,142 @@ mod tests { task.abort(); } + #[tokio::test] + async fn test_manage_retry_while_idle() { + use crate::dbm::DBM; + // Let's try adding a tower, setting it to idle and send revocation data in all its forms + // This replicates the three types of data the retrier can receive: + // - Initialization (from db) with stale data + // - Regular (fresh) data from `on_commitment_revocation` + // - A wake up call with no data + + let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); + let (tx, rx) = unbounded_channel(); + + // Stale data is sent on WTClient initialization if found in the database. We'll force that to happen by populating the DB before initializing the WTClient + let (tower_sk, tower_pk) = cryptography::get_random_keypair(); + let tower_id = TowerId(tower_pk); + + let mut dbm = DBM::new(&tmp_path.path().to_path_buf().join("watchtowers_db.sql3")).unwrap(); + let receipt = get_random_registration_receipt(); + dbm.store_tower_record(tower_id, "http://unreachable.tower", &receipt) + .unwrap(); + + let appointment = generate_random_appointment(None); + dbm.store_pending_appointment(tower_id, &appointment) + .unwrap(); + + // Now we can create the WTClient and check that the data is pending + let wt_client = Arc::new(Mutex::new( + WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, + )); + + // Also create the retrier thread so retries can be managed + let wt_client_clone = wt_client.clone(); + let task = tokio::spawn(async move { + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + LONG_AUTO_RETRY_DELAY, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await + }); + + { + // After the retriers gives up, it should go idling and flag the tower as unreachable + tokio::time::sleep(Duration::from_secs((MAX_ELAPSED_TIME) as u64)).await; + let state = wt_client.lock().unwrap(); + assert!(state.get_retrier_status(&tower_id).unwrap().is_idle()); + + let tower = state.towers.get(&tower_id).unwrap(); + assert!(tower.pending_appointments.contains(&appointment.locator)); + assert_eq!(tower.status, TowerStatus::Unreachable); + } + + // With the retrier idling all fresh data sent to it will be stored but it won't trigger a retry. + // (we can check the data was stored later on) + let new_appointment = generate_random_appointment(None); + wt_client + .lock() + .unwrap() + .add_pending_appointment(tower_id, &new_appointment); + tx.send((tower_id, RevocationData::Fresh(new_appointment.locator))) + .unwrap(); + + { + tokio::time::sleep(Duration::from_secs(2)).await; + let state = wt_client.lock().unwrap(); + assert!(state.get_retrier_status(&tower_id).unwrap().is_idle()); + let tower = state.towers.get(&tower_id).unwrap(); + assert_eq!(tower.status, TowerStatus::Unreachable); + } + + let mut add_appointment_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + + // Mock a proper response + let server = MockServer::start(); + add_appointment_receipt.sign(&tower_sk); + let add_appointment_response = + get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); + let api_mock = server.mock(|when, then| { + when.method(POST).path("/add_appointment"); + then.status(200) + .header("content-type", "application/json") + .json_body(json!(add_appointment_response)); + }); + + // Patch the tower address + wt_client + .lock() + .unwrap() + .towers + .get_mut(&tower_id) + .unwrap() + .net_addr = server.base_url(); + + // Check pending data is still there now, and is it not once the retrier succeeds + assert_eq!( + wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .len(), + 2, + ); + + // Send a retry flag to the retrier to force a retry. + tx.send((tower_id, RevocationData::None)).unwrap(); + + tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + // FIXME: Here we should be able to check this, however, due to httpmock limitations, we cannot return a response based on the request. + // Therefore, both requests will be responded with the same data. Given pending_appointments is a HashSet, we cannot even know which request + // will be sent first (sets are initialized with a random state, which decided the order or iteration). + // https://github.com/alexliesenfeld/httpmock/issues/49 + // assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + // assert!(wt_client + // .lock() + // .unwrap() + // .towers + // .get(&tower_id) + // .unwrap() + // .pending_appointments + // .is_empty()); + + // This is not much tbh, but looks like its the best we can do at the moment without experiencing random errors. + // Depending on what appointment is sent first the api will be hit either one or two times. + assert!(api_mock.hits() >= 1 && api_mock.hits() <= 2); + task.abort(); + } + #[tokio::test] async fn test_retry_tower() { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 6f25a83..0a9189f 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -111,7 +111,36 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" -def test_retry_watchtower(node_factory, bitcoind, teosd): +def test_auto_retry_watchtower(node_factory, bitcoind, teosd): + # The plugin is set to give up on retrying straight-away so we can test this fast. + l1, l2 = node_factory.line_graph( + 2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True, "watchtower-max-retry-time": 1, "watchtower-auto-retry-delay": 1}] + ) + + # We need to register l2 with the tower + tower_id = teosd.cli.gettowerinfo()["tower_id"] + l2.rpc.registertower(tower_id) + + # Stop the tower + teosd.stop() + + # Make a new payment with an unreachable tower + l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) + + # Wait until the tower has been flagged as unreachable + l2.daemon.wait_for_log(f"Starting to idle") + assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" + assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] + + # Start the tower and retry it + teosd.start() + + l2.daemon.wait_for_log(f"Finished idling. Flagging {tower_id} for retry") + l2.daemon.wait_for_log(f"Retry strategy succeeded for {tower_id}") + assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" + + +def test_manually_retry_watchtower(node_factory, bitcoind, teosd): # The plugin is set to give up on retrying straight-away so we can test this fast. l1, l2 = node_factory.line_graph( 2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True, "watchtower-max-retry-time": 0}] @@ -128,14 +157,16 @@ def test_retry_watchtower(node_factory, bitcoind, teosd): l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) # Wait until the tower has been flagged as unreachable - l2.daemon.wait_for_log(f"Setting {tower_id} as unreachable") + l2.daemon.wait_for_log(f"Starting to idle") assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] # Start the tower and retry it teosd.start() + # Manual retry l2.rpc.retrytower(tower_id) + l2.daemon.wait_for_log(f"Manually finished idling. Flagging {tower_id} for retry") l2.daemon.wait_for_log(f"Retry strategy succeeded for {tower_id}") assert l2.rpc.gettowerinfo(tower_id)["status"] == "reachable" From 09b65ef5139d2ad4701e5674d822c82b7f03375b Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 22 Dec 2022 18:12:36 +0100 Subject: [PATCH 064/119] Fixes bug regarding re-registering The re-register logic had a bug where the `TowerSummary` that was kept in memory after re-registering was sweeping the references to both the pending and invalid appointments. While this data was still in the database, re-registering may have made it look like the data was not there. --- watchtower-plugin/src/lib.rs | 14 ++++++++++++++ watchtower-plugin/src/wt_client.rs | 20 +++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index fd43b33..d9bc6f8 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -155,6 +155,20 @@ impl TowerSummary { self.status = status; self } + + /// Updates the main information about the summary while preserving the appointment maps. + pub fn udpate( + &mut self, + net_addr: String, + available_slots: u32, + subscription_start: u32, + subscription_expiry: u32, + ) { + self.net_addr = net_addr; + self.available_slots = available_slots; + self.subscription_start = subscription_start; + self.subscription_expiry = subscription_expiry; + } } impl From for TowerSummary { diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 9811543..2d9bddc 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -150,15 +150,25 @@ impl WTClient { self.dbm .store_tower_record(tower_id, tower_net_addr, receipt) .unwrap(); - self.towers.insert( - tower_id, - TowerSummary::new( + + if let Some(summary) = self.towers.get_mut(&tower_id) { + summary.udpate( tower_net_addr.to_owned(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry(), - ), - ); + ); + } else { + self.towers.insert( + tower_id, + TowerSummary::new( + tower_net_addr.to_owned(), + receipt.available_slots(), + receipt.subscription_start(), + receipt.subscription_expiry(), + ), + ); + }; Ok(()) } From 39e817c51bba88bdcdbbe83075da02f135cd7153 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 22 Dec 2022 18:14:37 +0100 Subject: [PATCH 065/119] Fixes clippy related issues --- teos/src/api/http.rs | 2 +- teos/src/api/internal.rs | 6 +----- teos/src/config.rs | 2 +- teos/src/watcher.rs | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 9425045..aa7eb62 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -219,7 +219,7 @@ async fn get_subscription_info( fn router( grpc_conn: PublicTowerServicesClient, -) -> impl Filter + Clone { +) -> impl Filter + Clone { let register = warp::post() .and(warp::path("register")) .and(warp::body::content_length_limit(REGISTER_BODY_LEN).and(warp::body::json())) diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index bcfe5b1..0edcde3 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -351,11 +351,7 @@ impl PrivateTowerServices for Arc { Some(info) => Ok(Response::new(msgs::GetUserResponse { available_slots: info.available_slots, subscription_expiry: info.subscription_expiry, - appointments: info - .appointments - .iter() - .map(|(uuid, _)| uuid.to_vec()) - .collect(), + appointments: info.appointments.keys().map(|uuid| uuid.to_vec()).collect(), })), None => Err(Status::new(Code::NotFound, "User not found")), } diff --git a/teos/src/config.rs b/teos/src/config.rs index 70d9278..54e7a99 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -17,7 +17,7 @@ pub fn data_dir_absolute_path(data_dir: String) -> PathBuf { } pub fn from_file(path: PathBuf) -> T { - match std::fs::read(&path) { + match std::fs::read(path) { Ok(file_content) => toml::from_slice::(&file_content).map_or_else( |e| { eprintln!("Couldn't parse config file: {}", e); diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 5d8b958..a9672a1 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -1663,7 +1663,7 @@ mod tests { watcher.last_known_block_height.load(Ordering::Relaxed), chain.get_block_count() ); - watcher.block_connected(&chain.generate(None), chain.get_block_count() as u32); + watcher.block_connected(&chain.generate(None), chain.get_block_count()); assert_eq!( watcher.last_known_block_height.load(Ordering::Relaxed), chain.get_block_count() From 31defe24f7060aafbcf4c53ce35c0ef0caf4328e Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 2 Jan 2023 11:43:33 +0100 Subject: [PATCH 066/119] Patches watchtower-plugin test_get_appointment e2e test_get_appointment fails when the plugin is asked about a given tracker before the tower is able to process the appointment to tracker transition. This doesn't happen consistently, but we've seen the error popping up every now and then. Simply add a log based waiting to make sure the state transition is performed before asserting. --- watchtower-plugin/tests/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 0a9189f..732f3d4 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -212,6 +212,7 @@ def test_get_appointment(node_factory, bitcoind, teosd, directory): # And after. Now this should be a tracker bitcoind.generate_block() + teosd.wait_for_log("New tracker added") tracker = l2.rpc.getappointment(tower_id, locator)["appointment"] assert "dispute_txid" in tracker and "penalty_txid" in tracker and "penalty_rawtx" in tracker From c3b735dec7d3314b3a33e66bddd5d85e99106af7 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 10 Jan 2023 13:57:38 +0100 Subject: [PATCH 067/119] Bumps cln-plugin version to v0.1.2 in watchtower-plugin --- Cargo.lock | 183 ++++++++++++++++++++++++++++------- watchtower-plugin/Cargo.toml | 2 +- 2 files changed, 147 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a902c78..e28124a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -546,13 +546,12 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb53d11b6ca3ecd28804c12ec7473700a21e2d4c2d17f5af8ed35bf18bce7e8" +checksum = "49be99e6e5ad55d420884b5b2a68aca890bcd1a1540ed6d2892363623a60f538" dependencies = [ "anyhow", "bytes 1.1.0", - "cln-rpc", "env_logger", "futures", "log", @@ -560,26 +559,7 @@ dependencies = [ "serde_json", "tokio 1.20.1", "tokio-stream", - "tokio-util 0.6.9", -] - -[[package]] -name = "cln-rpc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57be2b864deacdd001c8e5c4e67947e2edbb71f697edf86b7bbb04a66eab3ef4" -dependencies = [ - "anyhow", - "bitcoin_hashes", - "bytes 1.1.0", - "futures-util", - "hex", - "log", - "secp256k1", - "serde", - "serde_json", - "tokio 1.20.1", - "tokio-util 0.6.9", + "tokio-util 0.7.0", ] [[package]] @@ -861,17 +841,38 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -1219,6 +1220,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -1434,6 +1444,16 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1449,6 +1469,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "isahc" version = "1.7.2" @@ -1637,9 +1669,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libnghttp2-sys" @@ -1708,6 +1740,12 @@ dependencies = [ "tokio 1.20.1", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.6" @@ -1789,7 +1827,7 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -1909,7 +1947,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -2515,6 +2553,20 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -2553,7 +2605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -3800,43 +3852,100 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" version = "0.10.1" diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index d2421a6..20504a3 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning bitcoin = "0.28.0" -cln-plugin = "0.1.1" +cln-plugin = "0.1.2" # Local teos-common = { path = "../teos-common" } From 6920c9bf98549bb16cb80a0a0d3823f8fb3c7879 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 10 Jan 2023 15:19:45 +0100 Subject: [PATCH 068/119] Simplifies the cln plugin option parsing Options include helper functions to convert them to their proper type after `cln-plugin=0.1.2`. Use that to reduce the option parsing boilerplate. Also move all names, descriptions and default values for options, rpc_methods and hooks to a new file. --- watchtower-plugin/src/constants.rs | 52 +++++++++ watchtower-plugin/src/lib.rs | 1 + watchtower-plugin/src/main.rs | 173 +++++++++++++++-------------- 3 files changed, 145 insertions(+), 81 deletions(-) create mode 100644 watchtower-plugin/src/constants.rs diff --git a/watchtower-plugin/src/constants.rs b/watchtower-plugin/src/constants.rs new file mode 100644 index 0000000..b450cd3 --- /dev/null +++ b/watchtower-plugin/src/constants.rs @@ -0,0 +1,52 @@ +// Collection of ENV variable names and values +pub const TOWERS_DATA_DIR: &str = "TOWERS_DATA_DIR"; +pub const DEFAULT_TOWERS_DATA_DIR: &str = ".watchtower"; + +/// Collections of plugin option names, default values and descriptions + +pub const WT_PORT: &str = "watchtower-port"; +pub const DEFAULT_WT_PORT: i64 = 9814; +pub const WT_PORT_DESC: &str = "tower API port"; +pub const WT_MAX_RETRY_TIME: &str = "watchtower-max-retry-time"; +pub const DEFAULT_WT_MAX_RETRY_TIME: i64 = 900; +pub const WT_MAX_RETRY_TIME_DESC: &str = "the time (in seconds) after when a retrier will give up trying to send data to a temporary unreachable tower"; +pub const WT_PROXY: &str = "watchtower-proxy"; +pub const WT_PROXY_DESC: &str = "Socks v5 proxy IP address and port for the watchtower client"; +pub const WT_AUTO_RETRY_DELAY: &str = "watchtower-auto-retry-delay"; +pub const DEFAULT_WT_AUTO_RETRY_DELAY: i64 = 86400; +pub const WT_AUTO_RETRY_DELAY_DESC: &str = "the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day"; +pub const DEV_WT_MAX_RETRY_INTERVAL: &str = "dev-watchtower-max-retry-interval"; +pub const DEFAULT_DEV_WT_MAX_RETRY_INTERVAL: i64 = 60; +pub const DEV_WT_MAX_RETRY_INTERVAL_DESC: &str = + "the maximum time (in seconds) for a retrier wait interval"; + +/// Collections of rpc method names and descriptions + +pub const RPC_REGISTER_TOWER: &str = "registertower"; +pub const RPC_REGISTER_TOWER_DESC: &str = + "Registers the client public key (user id) with the tower"; +pub const RPC_GET_REGISTRATION_RECEIPT: &str = "getregistrationreceipt"; +pub const RPC_GET_REGISTRATION_RECEIPT_DESC: &str = + "Gets the latest registration receipt given a tower id"; +pub const RPC_GET_APPOINTMENT: &str = "getappointment"; +pub const RPC_GET_APPOINTMENT_DESC: &str = + "Gets appointment data from the tower given a tower id and a locator"; +pub const RPC_GET_APPOINTMENT_RECEIPT: &str = "getappointmentreceipt"; +pub const RPC_GET_APPOINTMENT_RECEIPT_DESC: &str = + "Gets a (local) appointment receipt given a tower id and a locator"; +pub const RPC_GET_SUBSCRIPTION_INFO: &str = "getsubscriptioninfo"; +pub const RPC_GET_SUBSCRIPTION_INFO_DESC: &str = + "Gets the subscription information directly from the tower"; +pub const RPC_LIST_TOWERS: &str = "listtowers"; +pub const RPC_LIST_TOWERS_DESC: &str = "Lists all registered towers"; +pub const RPC_GET_TOWER_INFO: &str = "gettowerinfo"; +pub const RPC_GET_TOWER_INFO_DESC: &str = "Shows the info about a tower given a tower id"; +pub const RPC_RETRY_TOWER: &str = "retrytower"; +pub const RPC_RETRY_TOWER_DESC: &str = + "Retries to send pending appointment to an unreachable tower"; +pub const RPC_ABANDON_TOWER: &str = "abandontower"; +pub const RPC_ABANDON_TOWER_DESC: &str = "Forgets about a tower and wipes all local data"; + +/// Collections of hook names + +pub const HOOK_COMMITMENT_REVOCATION: &str = "commitment_revocation"; diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index d9bc6f8..5e2c838 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -7,6 +7,7 @@ use teos_common::appointment::{Appointment, Locator}; use teos_common::receipts::AppointmentReceipt; use teos_common::TowerId; +pub mod constants; pub mod convert; pub mod dbm; pub mod net; diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index ab89058..1fb9596 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -22,7 +22,7 @@ use watchtower_plugin::net::http::{ }; use watchtower_plugin::retrier::RetryManager; use watchtower_plugin::wt_client::{RevocationData, WTClient}; -use watchtower_plugin::TowerStatus; +use watchtower_plugin::{constants, TowerStatus}; fn to_cln_error(e: RequestError) -> Error { let e = match e { @@ -75,15 +75,9 @@ async fn register( // Otherwise the tower could just generate a subscription starting far in the future. For this we need to access lightning RPC // which is not available in the current version of `cln-plugin` (but already on master). Add it for the next release. - // FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap - // given that we are certain the option exists. let port = params.port.unwrap_or( - if let Value::Integer(x) = plugin.option("watchtower-port").unwrap() { - x as u16 - } else { - // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 9814 - }, + u16::try_from(plugin.option(constants::WT_PORT).unwrap().as_i64().unwrap()) + .map_err(|_| anyhow!("{} out of range", constants::WT_PORT))?, ); let mut tower_net_addr = format!("{}:{}", host, port); @@ -521,76 +515,86 @@ async fn on_commitment_revocation( #[tokio::main] async fn main() -> Result<(), Error> { - let data_dir = match env::var("TOWERS_DATA_DIR") { + let data_dir = match env::var(constants::TOWERS_DATA_DIR) { Ok(v) => PathBuf::from(v), - Err(_) => home_dir().unwrap().join(".watchtower"), + Err(_) => home_dir().unwrap().join(constants::DEFAULT_TOWERS_DATA_DIR), }; let builder = Builder::new(stdin(), stdout()) .option(ConfigOption::new( - "watchtower-port", - Value::Integer(9814), - "tower API port", + constants::WT_PORT, + Value::Integer(constants::DEFAULT_WT_PORT), + constants::WT_PORT_DESC, )) .option(ConfigOption::new( - "watchtower-max-retry-time", - Value::Integer(900), - "the time (in seconds) after where the retrier will give up trying to send data to a temporary unreachable tower", + constants::WT_MAX_RETRY_TIME, + Value::Integer(constants::DEFAULT_WT_MAX_RETRY_TIME), + constants::WT_MAX_RETRY_TIME_DESC, )) .option(ConfigOption::new( - "watchtower-proxy", + constants::WT_PROXY, Value::OptString, - "Socks v5 proxy IP address and port for the watchtower client", - )).option(ConfigOption::new( - "watchtower-auto-retry-delay", - Value::Integer(86400), - "the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day", - )) + constants::WT_PROXY_DESC, + )) .option(ConfigOption::new( - "dev-watchtower-max-retry-interval", - Value::Integer(60), - "the maximum time (in seconds) for a retrier wait interval", + constants::WT_AUTO_RETRY_DELAY, + Value::Integer(constants::DEFAULT_WT_AUTO_RETRY_DELAY), + constants::WT_AUTO_RETRY_DELAY_DESC, + )) + .option(ConfigOption::new( + constants::DEV_WT_MAX_RETRY_INTERVAL, + Value::Integer(constants::DEFAULT_DEV_WT_MAX_RETRY_INTERVAL), + constants::DEV_WT_MAX_RETRY_INTERVAL_DESC, )) .rpcmethod( - "registertower", - "Registers the client public key (user id) with the tower.", + constants::RPC_REGISTER_TOWER, + constants::RPC_REGISTER_TOWER_DESC, register, - ).rpcmethod( - "getregistrationreceipt", - "Gets the latest registration receipt given a tower id.", + ) + .rpcmethod( + constants::RPC_GET_REGISTRATION_RECEIPT, + constants::RPC_GET_REGISTRATION_RECEIPT_DESC, get_registration_receipt, ) .rpcmethod( - "getappointment", - "Gets appointment data from the tower given the tower id and the locator.", + constants::RPC_GET_APPOINTMENT, + constants::RPC_GET_APPOINTMENT_DESC, get_appointment, - ).rpcmethod( - "getappointmentreceipt", - "Gets a (local) appointment receipt given a tower id and an locator.", + ) + .rpcmethod( + constants::RPC_GET_APPOINTMENT_RECEIPT, + constants::RPC_GET_APPOINTMENT_RECEIPT_DESC, get_appointment_receipt, ) .rpcmethod( - "getsubscriptioninfo", - "Gets the subscription information directly from the tower.", + constants::RPC_GET_SUBSCRIPTION_INFO, + constants::RPC_GET_SUBSCRIPTION_INFO_DESC, get_subscription_info, ) - .rpcmethod("listtowers", "Lists all registered towers.", list_towers) .rpcmethod( - "gettowerinfo", - "Shows the info about a given tower.", + constants::RPC_LIST_TOWERS, + constants::RPC_LIST_TOWERS_DESC, + list_towers, + ) + .rpcmethod( + constants::RPC_GET_TOWER_INFO, + constants::RPC_GET_TOWER_INFO_DESC, get_tower_info, ) .rpcmethod( - "retrytower", - "Retries to send pending appointment to an unreachable tower.", + constants::RPC_RETRY_TOWER, + constants::RPC_RETRY_TOWER_DESC, retry_tower, ) .rpcmethod( - "abandontower", - "Forgets about a tower and wipes all local data.", + constants::RPC_ABANDON_TOWER, + constants::RPC_ABANDON_TOWER_DESC, abandon_tower, ) - .hook("commitment_revocation", on_commitment_revocation); + .hook( + constants::HOOK_COMMITMENT_REVOCATION, + on_commitment_revocation, + ); // We're unwrapping here given it does not seem we actually have anything to check at the moment. // Change this so the plugin can be disabled soon if this happens not to be the case. @@ -602,41 +606,48 @@ async fn main() -> Result<(), Error> { let (tx, rx) = unbounded_channel(); let wt_client = Arc::new(Mutex::new(WTClient::new(data_dir, tx).await)); - // FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap - // given that we are certain the option exists. - wt_client.lock().unwrap().proxy = - if let Value::String(x) = midstate.option("watchtower-proxy").unwrap() { - if !x.is_empty() { - Some(x) - } else { - None - } - } else { - None - }; - let max_elapsed_time = - if let Value::Integer(x) = midstate.option("watchtower-max-retry-time").unwrap() { - x as u16 - } else { - // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 900 - }; - let auto_retry_delay = - if let Value::Integer(x) = midstate.option("watchtower-auto-retry-delay").unwrap() { - x as u16 - } else { - // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 3600 - }; - let max_interval_time = if let Value::Integer(x) = midstate - .option("dev-watchtower-max-retry-interval") + + wt_client.lock().unwrap().proxy = midstate + .option(constants::WT_PROXY) .unwrap() - { - x as u16 - } else { - // We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme. - 60 - }; + .as_str() + .map(|x| x.to_owned()); + + let max_elapsed_time = u16::try_from( + midstate + .option(constants::WT_MAX_RETRY_TIME) + .unwrap() + .as_i64() + .unwrap(), + ) + .map_err(|e| { + log::error!("{} out of range", constants::WT_MAX_RETRY_TIME); + e + })?; + + let auto_retry_delay = u16::try_from( + midstate + .option(constants::WT_AUTO_RETRY_DELAY) + .unwrap() + .as_i64() + .unwrap(), + ) + .map_err(|e| { + log::error!("{} out of range", constants::WT_AUTO_RETRY_DELAY); + e + })?; + + let max_interval_time = u16::try_from( + midstate + .option(constants::DEV_WT_MAX_RETRY_INTERVAL) + .unwrap() + .as_i64() + .unwrap(), + ) + .map_err(|e| { + log::error!("{} out of range", constants::DEV_WT_MAX_RETRY_INTERVAL); + e + })?; let plugin = midstate.start(wt_client.clone()).await?; tokio::spawn(async move { From 1a076f92eaac6ace4c2efc6a9257d6a95b089df2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 11 Jan 2023 11:27:35 +0100 Subject: [PATCH 069/119] Fixes auto-retry-delay type `auto-retry-delay` was set to be u16, but its default value was beyond u16::MAX. --- watchtower-plugin/src/main.rs | 2 +- watchtower-plugin/src/retrier.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 1fb9596..019ac85 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -625,7 +625,7 @@ async fn main() -> Result<(), Error> { e })?; - let auto_retry_delay = u16::try_from( + let auto_retry_delay = u32::try_from( midstate .option(constants::WT_AUTO_RETRY_DELAY) .unwrap() diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index f3c0173..d9bb562 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -49,7 +49,7 @@ pub struct RetryManager { wt_client: Arc>, unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>, max_elapsed_time_secs: u16, - auto_retry_delay: u16, + auto_retry_delay: u32, max_interval_time_secs: u16, retriers: HashMap>, } @@ -59,7 +59,7 @@ impl RetryManager { wt_client: Arc>, unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>, max_elapsed_time_secs: u16, - auto_retry_delay: u16, + auto_retry_delay: u32, max_interval_time_secs: u16, ) -> Self { RetryManager { @@ -595,8 +595,8 @@ mod tests { use crate::net::http::ApiError; use crate::test_utils::get_dummy_add_appointment_response; - const LONG_AUTO_RETRY_DELAY: u16 = 60; - const SHORT_AUTO_RETRY_DELAY: u16 = 3; + const LONG_AUTO_RETRY_DELAY: u32 = 60; + const SHORT_AUTO_RETRY_DELAY: u32 = 3; const API_DELAY: f64 = 0.5; const MAX_ELAPSED_TIME: u16 = 2; const MAX_INTERVAL_TIME: u16 = 1; From d50ce639bf3f1e8d32d2477859b7ebd0c0498836 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 11 Jan 2023 13:22:56 +0100 Subject: [PATCH 070/119] Improves tor flag logic in watchtower-plugin The `watchtower-plugin` was specifying a custom tor flag to signal whether Tor may be used by the client. This was due to `cln-plugin (v0.1.1-)` not allowing plugins to access the CoreLN configuration options and, therefore, our plugin was unable to fetch the `proxy` / `always-use-proxy` options. This fetches the aforementioned options and revamps the logic to comply with the `always-use-proxy` requirements, that is, if the flag is set all communications must be performed using Tor. Also, it replaces some of the currently used `String`s for more meaningful types to store network data (such as `AddressType`, `NetAddress`, or `ProxyInfo`). This drops our custom `watchtower-proxy` config option --- teos-common/src/net.rs | 75 ++++++++++++++++ watchtower-plugin/src/lib.rs | 20 +++-- watchtower-plugin/src/main.rs | 65 +++++++------- watchtower-plugin/src/net/http.rs | 127 +++++++++++++++------------- watchtower-plugin/src/net/mod.rs | 24 ++++++ watchtower-plugin/src/retrier.rs | 8 +- watchtower-plugin/src/wt_client.rs | 53 ++++++------ watchtower-plugin/tests/conftest.py | 4 +- watchtower-plugin/tests/test.py | 27 +++++- 9 files changed, 270 insertions(+), 133 deletions(-) diff --git a/teos-common/src/net.rs b/teos-common/src/net.rs index e0497a7..2831ae2 100644 --- a/teos-common/src/net.rs +++ b/teos-common/src/net.rs @@ -1,6 +1,8 @@ +use serde::Serialize; use std::fmt; /// Represents all types of teos network addresses +#[derive(Clone, Serialize, Debug, PartialEq, Eq)] pub enum AddressType { IpV4 = 0, TorV3 = 1, @@ -37,3 +39,76 @@ impl fmt::Display for AddressType { write!(f, "{}", s) } } + +impl AddressType { + pub fn get_type(net_addr: &str) -> AddressType { + if net_addr.contains(".onion:") { + AddressType::TorV3 + } else { + AddressType::IpV4 + } + } + + pub fn is_tor(&self) -> bool { + self == &AddressType::TorV3 + } + + pub fn is_clearnet(&self) -> bool { + self == &AddressType::IpV4 + } +} + +#[derive(Clone, Serialize, Debug, PartialEq, Eq)] +pub struct NetAddr { + net_addr: String, + #[serde(skip)] + addr_type: AddressType, +} + +impl NetAddr { + pub fn new(net_addr: String) -> Self { + NetAddr { + addr_type: AddressType::get_type(&net_addr), + net_addr, + } + } + + pub fn net_addr(&self) -> &str { + &self.net_addr + } + + pub fn addr_type(&self) -> &AddressType { + &self.addr_type + } + + pub fn is_onion(&self) -> bool { + self.addr_type().is_tor() + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + pub const TORV3_ADDR: &str = + "recnedb7xfhzjdrcgxongzli3a6qyrv5jwgowoho3v5g3rwk7kkglrid.onion:9814"; + pub const IPV4_ADDR: &str = "teos.talaia.watch:9814"; + + #[test] + fn test_get_type() { + assert_eq!(AddressType::get_type(TORV3_ADDR), AddressType::TorV3); + assert_eq!(AddressType::get_type(IPV4_ADDR), AddressType::IpV4); + } + + #[test] + fn test_is_tor() { + assert!(NetAddr::new(TORV3_ADDR.to_owned()).addr_type.is_tor()); + assert!(!NetAddr::new(IPV4_ADDR.to_owned()).addr_type.is_tor()); + } + + #[test] + fn test_is_clearnet() { + assert!(!NetAddr::new(TORV3_ADDR.to_owned()).addr_type.is_clearnet()); + assert!(NetAddr::new(IPV4_ADDR.to_owned()).addr_type.is_clearnet()); + } +} diff --git a/watchtower-plugin/src/lib.rs b/watchtower-plugin/src/lib.rs index 5e2c838..8f41658 100755 --- a/watchtower-plugin/src/lib.rs +++ b/watchtower-plugin/src/lib.rs @@ -4,6 +4,7 @@ use std::fmt; use serde::Serialize; use teos_common::appointment::{Appointment, Locator}; +use teos_common::net::NetAddr; use teos_common::receipts::AppointmentReceipt; use teos_common::TowerId; @@ -101,7 +102,8 @@ impl TowerStatus { /// Summarized data associated with a given tower. #[derive(Clone, Serialize, Debug, PartialEq, Eq)] pub struct TowerSummary { - pub net_addr: String, + #[serde(flatten)] + pub net_addr: NetAddr, pub available_slots: u32, subscription_start: u32, pub subscription_expiry: u32, @@ -121,7 +123,7 @@ impl TowerSummary { subscription_expiry: u32, ) -> Self { Self { - net_addr, + net_addr: NetAddr::new(net_addr), available_slots, subscription_start, subscription_expiry, @@ -141,7 +143,7 @@ impl TowerSummary { invalid_appointments: HashSet, ) -> Self { Self { - net_addr, + net_addr: NetAddr::new(net_addr), available_slots, subscription_start, subscription_expiry, @@ -165,7 +167,7 @@ impl TowerSummary { subscription_start: u32, subscription_expiry: u32, ) { - self.net_addr = net_addr; + self.net_addr = NetAddr::new(net_addr); self.available_slots = available_slots; self.subscription_start = subscription_start; self.subscription_expiry = subscription_expiry; @@ -364,6 +366,12 @@ mod tests { use teos_common::test_utils::generate_random_appointment; + impl TowerSummary { + pub fn set_net_addr(&mut self, net_addr: String) { + self.net_addr = NetAddr::new(net_addr); + } + } + #[test] fn test_new() { let net_addr: String = "addr".to_owned(); @@ -377,7 +385,7 @@ mod tests { assert_eq!( tower_summary, TowerSummary { - net_addr, + net_addr: NetAddr::new(net_addr), available_slots: AVAILABLE_SLOTS, subscription_start: SUBSCRIPTION_START, subscription_expiry: SUBSCRIPTION_EXPIRY, @@ -408,7 +416,7 @@ mod tests { assert_eq!( tower_summary, TowerSummary { - net_addr, + net_addr: NetAddr::new(net_addr), available_slots: AVAILABLE_SLOTS, subscription_start: SUBSCRIPTION_START, subscription_expiry: SUBSCRIPTION_EXPIRY, diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 019ac85..0f96e7d 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -12,6 +12,7 @@ use cln_plugin::options::{ConfigOption, Value}; use cln_plugin::{anyhow, Builder, Error, Plugin}; use teos_common::appointment::{Appointment, Locator}; +use teos_common::net::NetAddr; use teos_common::protos as common_msgs; use teos_common::TowerId; use teos_common::{cryptography, errors}; @@ -20,6 +21,7 @@ use watchtower_plugin::convert::{CommitmentRevocation, GetAppointmentParams, Reg use watchtower_plugin::net::http::{ self, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, }; +use watchtower_plugin::net::ProxyInfo; use watchtower_plugin::retrier::RetryManager; use watchtower_plugin::wt_client::{RevocationData, WTClient}; use watchtower_plugin::{constants, TowerStatus}; @@ -67,7 +69,7 @@ async fn register( v: serde_json::Value, ) -> Result { let params = RegisterParams::try_from(v).map_err(|x| anyhow!(x))?; - let host = params.host.unwrap_or_else(|| "localhost".to_owned()); + let mut host = params.host.unwrap_or_else(|| "localhost".to_owned()); let tower_id = params.tower_id; let user_id = plugin.state().lock().unwrap().user_id; @@ -80,14 +82,16 @@ async fn register( .map_err(|_| anyhow!("{} out of range", constants::WT_PORT))?, ); - let mut tower_net_addr = format!("{}:{}", host, port); - if !tower_net_addr.starts_with("http") { - tower_net_addr = format!("http://{}", tower_net_addr) - } + let tower_net_addr = { + if !host.starts_with("http://") { + host = format!("http://{}", host) + } + NetAddr::new(format!("{}:{}", host, port)) + }; let proxy = plugin.state().lock().unwrap().proxy.clone(); - let receipt = http::register(tower_id, user_id, &tower_net_addr, proxy) + let receipt = http::register(tower_id, user_id, &tower_net_addr, &proxy) .await .map_err(|e| { let mut state = plugin.state().lock().unwrap(); @@ -107,7 +111,7 @@ async fn register( .state() .lock() .unwrap() - .add_update_tower(tower_id, &tower_net_addr, &receipt).map_err(|e| { + .add_update_tower(tower_id, tower_net_addr.net_addr(), &receipt).map_err(|e| { if e.is_expiry() { anyhow!("Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for") } else { @@ -161,14 +165,14 @@ async fn get_subscription_info( } }?; - let get_subscription_info = format!("{}/get_subscription_info", tower_net_addr); let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk).unwrap(); let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( post_request( - &get_subscription_info, + &tower_net_addr, + "get_subscription_info", &common_msgs::GetSubscriptionInfoRequest { signature }, - proxy, + &proxy, ) .await, ) @@ -203,7 +207,6 @@ async fn get_appointment( } }?; - let get_appointment_endpoint = format!("{}/get_appointment", tower_net_addr); let signature = cryptography::sign( format!("get appointment {}", params.locator).as_bytes(), &user_sk, @@ -212,12 +215,13 @@ async fn get_appointment( let response: ApiResponse = process_post_response( post_request( - &get_appointment_endpoint, + &tower_net_addr, + "get_appointment", &common_msgs::GetAppointmentRequest { locator: params.locator.to_vec(), signature, }, - proxy, + &proxy, ) .await, ) @@ -411,14 +415,7 @@ async fn on_commitment_revocation( for (tower_id, net_addr, status) in towers { if status.is_reachable() { - match http::add_appointment( - tower_id, - &net_addr, - proxy.clone(), - &appointment, - &signature, - ) - .await + match http::add_appointment(tower_id, &net_addr, &proxy, &appointment, &signature).await { Ok((slots, receipt)) => { plugin @@ -531,11 +528,6 @@ async fn main() -> Result<(), Error> { Value::Integer(constants::DEFAULT_WT_MAX_RETRY_TIME), constants::WT_MAX_RETRY_TIME_DESC, )) - .option(ConfigOption::new( - constants::WT_PROXY, - Value::OptString, - constants::WT_PROXY_DESC, - )) .option(ConfigOption::new( constants::WT_AUTO_RETRY_DELAY, Value::Integer(constants::DEFAULT_WT_AUTO_RETRY_DELAY), @@ -605,13 +597,20 @@ async fn main() -> Result<(), Error> { }; let (tx, rx) = unbounded_channel(); - let wt_client = Arc::new(Mutex::new(WTClient::new(data_dir, tx).await)); - - wt_client.lock().unwrap().proxy = midstate - .option(constants::WT_PROXY) - .unwrap() - .as_str() - .map(|x| x.to_owned()); + let wt_client = Arc::new(Mutex::new( + WTClient::with_proxy( + data_dir, + tx, + midstate.configuration().proxy.map(|proxy| { + // We don't need to inform `always-use-proxy` needing `proxy` to work. This is done by CLN already when needed. + ProxyInfo::new( + proxy, + midstate.configuration().always_use_proxy.unwrap_or(false), + ) + }), + ) + .await, + )); let max_elapsed_time = u16::try_from( midstate diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 40e77b2..2d7b187 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -3,10 +3,12 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use teos_common::appointment::Appointment; use teos_common::cryptography; +use teos_common::net::NetAddr; use teos_common::protos as common_msgs; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +use crate::net::ProxyInfo; use crate::MisbehaviorProof; /// Represents a generic api response. @@ -56,13 +58,14 @@ impl From for AddAppointmentError { pub async fn register( tower_id: TowerId, user_id: UserId, - tower_net_addr: &str, - proxy: Option, + tower_net_addr: &NetAddr, + proxy: &Option, ) -> Result { log::info!("Registering in the Eye of Satoshi (tower_id={})", tower_id); process_post_response( post_request( - &format!("{}/register", tower_net_addr), + tower_net_addr, + "register", &common_msgs::RegisterRequest { user_id: user_id.to_vec(), }, @@ -85,8 +88,8 @@ pub async fn register( /// Encapsulates the logging and response parsing of sending and appointment to the tower. pub async fn add_appointment( tower_id: TowerId, - tower_net_addr: &str, - proxy: Option, + tower_net_addr: &NetAddr, + proxy: &Option, appointment: &Appointment, signature: &str, ) -> Result<(u32, AppointmentReceipt), AddAppointmentError> { @@ -107,8 +110,8 @@ pub async fn add_appointment( /// Handles the logic of interacting with the `add_appointment` endpoint of the tower. pub async fn send_appointment( tower_id: TowerId, - tower_net_addr: &str, - proxy: Option, + tower_net_addr: &NetAddr, + proxy: &Option, appointment: &Appointment, signature: &str, ) -> Result<(common_msgs::AddAppointmentResponse, AppointmentReceipt), AddAppointmentError> { @@ -118,12 +121,7 @@ pub async fn send_appointment( }; match process_post_response( - post_request( - &format!("{}/add_appointment", tower_net_addr), - &request_data, - proxy, - ) - .await, + post_request(tower_net_addr, "add_appointment", &request_data, proxy).await, ) .await? { @@ -152,40 +150,50 @@ pub async fn send_appointment( /// Generic function to post different types of requests to the tower. pub async fn post_request( + tower_net_addr: &NetAddr, endpoint: &str, data: S, - proxy: Option, + proxy: &Option, ) -> Result { - let url = reqwest::Url::parse(endpoint).map_err(|e| { - RequestError::ConnectionError(format!("Cannot connect to the given URL. {}", e)) - })?; - let client = if url.host_str().unwrap().ends_with(".onion") { - if let Some(proxy) = proxy { - let proxy = reqwest::Proxy::http(format!("socks5h://{}", proxy)) - .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?; + let client = if let Some(proxy) = proxy { + if proxy.always_use || tower_net_addr.is_onion() { reqwest::Client::builder() - .proxy(proxy) + .proxy( + reqwest::Proxy::http(proxy.get_socks_addr()) + .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?, + ) .build() .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? } else { + reqwest::Client::new() + } + } else { + // If there is no proxy we only build the client as long as the address is not onion + if tower_net_addr.is_onion() { return Err(RequestError::ConnectionError( "Cannot connect to an onion address without a proxy".to_owned(), )); } - } else { reqwest::Client::new() }; - client.post(endpoint).json(&data).send().await.map_err(|e| { - log::debug!("An error ocurred when sending data to the tower: {}", e); - if e.is_connect() | e.is_timeout() { - RequestError::ConnectionError( - "Cannot connect to the tower. Connection refused".to_owned(), - ) - } else { - RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".to_owned()) - } - }) + client + .post(format!("{}/{}", tower_net_addr.net_addr(), endpoint)) + .json(&data) + .send() + .await + .map_err(|e| { + log::debug!("An error ocurred when sending data to the tower: {}", e); + if e.is_connect() | e.is_timeout() { + RequestError::ConnectionError( + "Cannot connect to the tower. Connection refused".to_owned(), + ) + } else { + RequestError::Unexpected( + "Unexpected error ocurred (see logs for more info)".to_owned(), + ) + } + }) } /// Generic function to process the response of a given post request. @@ -250,8 +258,8 @@ mod tests { let receipt = register( TowerId(tower_pk), registration_receipt.user_id(), - &format!("http://{}", server.address()), - None, + &NetAddr::new(server.base_url()), + &None, ) .await .unwrap(); @@ -265,8 +273,8 @@ mod tests { let error = register( get_random_user_id(), get_random_user_id(), - "http://server_addr", - None, + &NetAddr::new("http://server_addr".to_owned()), + &None, ) .await .unwrap_err(); @@ -287,8 +295,8 @@ mod tests { let error = register( get_random_user_id(), get_random_user_id(), - &format!("http://{}", server.address()), - None, + &NetAddr::new(server.base_url()), + &None, ) .await .unwrap_err(); @@ -318,8 +326,8 @@ mod tests { let (response, receipt) = add_appointment( TowerId(tower_pk), - &server.base_url(), - None, + &NetAddr::new(server.base_url()), + &None, &appointment, appointment_receipt.user_signature(), ) @@ -350,8 +358,8 @@ mod tests { let (response, receipt) = send_appointment( TowerId(tower_pk), - &server.base_url(), - None, + &NetAddr::new(server.base_url()), + &None, &appointment, appointment_receipt.user_signature(), ) @@ -383,8 +391,8 @@ mod tests { let tower_id = get_random_user_id(); let error = send_appointment( tower_id, - &server.base_url(), - None, + &NetAddr::new(server.base_url()), + &None, &appointment, appointment_receipt.user_signature(), ) @@ -410,8 +418,8 @@ mod tests { async fn test_send_appointment_connection_error() { let error = send_appointment( get_random_user_id(), - "http://server_addr", - None, + &NetAddr::new("http://server_addr".to_owned()), + &None, &generate_random_appointment(None), "user_sig", ) @@ -437,8 +445,8 @@ mod tests { let error = send_appointment( get_random_user_id(), - &server.base_url(), - None, + &NetAddr::new(server.base_url()), + &None, &generate_random_appointment(None), "user_sig", ) @@ -470,8 +478,8 @@ mod tests { let error = send_appointment( get_random_user_id(), - &server.base_url(), - None, + &NetAddr::new(server.base_url()), + &None, &generate_random_appointment(None), "user_sig", ) @@ -490,7 +498,7 @@ mod tests { then.status(200).header("content-type", "application/json"); }); - let response = post_request(&server.base_url(), json!(""), None) + let response = post_request(&NetAddr::new(server.base_url()), "", json!(""), &None) .await .unwrap(); @@ -500,12 +508,15 @@ mod tests { #[tokio::test] async fn test_post_request_connection_error() { - let unreachable_server_url = "http://server_addr"; - assert!(matches!( - post_request(unreachable_server_url, json!(""), None,) - .await - .unwrap_err(), + post_request( + &NetAddr::new("http://unreachable_url".to_owned()), + "", + json!(""), + &None, + ) + .await + .unwrap_err(), RequestError::ConnectionError { .. } )); } @@ -522,7 +533,7 @@ mod tests { // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( - post_request(&server.base_url(), json!(""), None).await, + post_request(&NetAddr::new(server.base_url()), "", json!(""), &None).await, ) .await .unwrap_err(); diff --git a/watchtower-plugin/src/net/mod.rs b/watchtower-plugin/src/net/mod.rs index 3883215..cdd2805 100644 --- a/watchtower-plugin/src/net/mod.rs +++ b/watchtower-plugin/src/net/mod.rs @@ -1 +1,25 @@ +use cln_plugin::messages; +use serde::Deserialize; pub mod http; + +#[derive(Clone, Debug, Deserialize)] +pub struct ProxyInfo { + #[serde(flatten)] + /// The proxy data + inner: messages::ProxyInfo, + /// Whether to only send data though Tor or not + pub always_use: bool, +} + +impl ProxyInfo { + pub fn new(proxy: messages::ProxyInfo, always_use: bool) -> Self { + Self { + inner: proxy, + always_use, + } + } + + pub fn get_socks_addr(&self) -> String { + format!("socks5h://{}:{}", self.inner.address, self.inner.port) + } +} diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index d9bb562..8f7c1ba 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -451,7 +451,7 @@ impl Retrier { // If the tower state is subscription_error we need to re-register first. If we cannot, then the retry is aborted. if status.is_subscription_error() { - let receipt = http::register(tower_id, user_id, &net_addr, proxy.clone()) + let receipt = http::register(tower_id, user_id, &net_addr, &proxy) .await .map_err(|e| { log::debug!("Cannot renew registration with tower. Error: {:?}", e); @@ -466,7 +466,7 @@ impl Retrier { self.wt_client .lock() .unwrap() - .add_update_tower(tower_id, &net_addr, &receipt) + .add_update_tower(tower_id, net_addr.net_addr(), &receipt) .map_err(|e| { let reason = if e.is_expiry() { "Registration receipt contains a subscription expiry that is not higher than the one we are currently registered for" @@ -491,7 +491,7 @@ impl Retrier { match http::add_appointment( tower_id, &net_addr, - proxy.clone(), + &proxy, &appointment, &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), ) @@ -1242,7 +1242,7 @@ mod tests { .towers .get_mut(&tower_id) .unwrap() - .net_addr = server.base_url(); + .set_net_addr(server.base_url()); // Check pending data is still there now, and is it not once the retrier succeeds assert_eq!( diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 2d9bddc..d49fa7f 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -13,6 +13,7 @@ use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::dbm::DBM; +use crate::net::ProxyInfo; use crate::retrier::RetrierStatus; use crate::{MisbehaviorProof, SubscriptionError, TowerInfo, TowerStatus, TowerSummary}; @@ -71,13 +72,21 @@ pub struct WTClient { /// The user identifier. pub user_id: UserId, /// Optional proxy - pub proxy: Option, + pub proxy: Option, } impl WTClient { pub async fn new( data_dir: PathBuf, unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, + ) -> Self { + Self::with_proxy(data_dir, unreachable_towers, None).await + } + + pub async fn with_proxy( + data_dir: PathBuf, + unreachable_towers: UnboundedSender<(TowerId, RevocationData)>, + proxy: Option, ) -> Self { // Create data dir if it does not exist fs::create_dir_all(&data_dir).await.unwrap_or_else(|e| { @@ -123,7 +132,7 @@ impl WTClient { dbm, user_sk, user_id, - proxy: None, + proxy, } } @@ -484,7 +493,6 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tower_net_addr = "talaia.watch".to_owned(); let locator = generate_random_appointment(None).locator; let registration_receipt = get_random_registration_receipt(); @@ -501,7 +509,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.clone(), + "talaia.watch".to_owned(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -510,7 +518,7 @@ mod tests { Vec::new(), ); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) .unwrap(); wt_client.add_appointment_receipt( tower_id, @@ -534,7 +542,6 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -545,7 +552,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.clone(), + "talaia.watch".to_owned(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -555,7 +562,7 @@ mod tests { ); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -578,7 +585,6 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -588,7 +594,7 @@ mod tests { // Add the tower to the state and try again wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -610,7 +616,6 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); @@ -621,7 +626,7 @@ mod tests { // Add the tower to the state and try again let tower_info = TowerInfo::new( - tower_net_addr.clone(), + "talaia.watch".to_owned(), registration_receipt.available_slots(), registration_receipt.subscription_start(), registration_receipt.subscription_expiry(), @@ -631,7 +636,7 @@ mod tests { ); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, &tower_info.net_addr, ®istration_receipt) .unwrap(); wt_client.add_invalid_appointment(tower_id, &appointment); @@ -650,13 +655,12 @@ mod tests { WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await; let tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch".to_owned(); let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); @@ -696,16 +700,16 @@ mod tests { let tower_id = get_random_user_id(); let another_tower_id = get_random_user_id(); - let tower_net_addr = "talaia.watch".to_owned(); + let tower_net_addr = "talaia.watch"; let registration_receipt = get_random_registration_receipt(); let appointment = generate_random_appointment(None); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, tower_net_addr, ®istration_receipt) .unwrap(); wt_client - .add_update_tower(another_tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(another_tower_id, tower_net_addr, ®istration_receipt) .unwrap(); wt_client.add_pending_appointment(tower_id, &appointment); wt_client.add_pending_appointment(another_tower_id, &appointment); @@ -770,7 +774,6 @@ mod tests { let (tower_sk, tower_pk) = cryptography::get_random_keypair(); let tower_id = TowerId(tower_pk); - let tower_net_addr = "talaia.watch".to_owned(); // If we call this on an unknown tower it will simply do nothing let appointment = generate_random_appointment(None); @@ -782,7 +785,7 @@ mod tests { // // Add the tower to the state and try again let registration_receipt = get_random_registration_receipt(); wt_client - .add_update_tower(tower_id, &tower_net_addr, ®istration_receipt) + .add_update_tower(tower_id, "talaia.watch", ®istration_receipt) .unwrap(); wt_client.flag_misbehaving_tower(tower_id, proof.clone()); @@ -875,17 +878,11 @@ mod tests { let (tower2_sk, tower2_pk) = cryptography::get_random_keypair(); let tower2_id = TowerId(tower2_pk); - let tower_info = TowerInfo::empty( - "talaia.watch".to_owned(), - receipt.available_slots(), - receipt.subscription_start(), - receipt.subscription_expiry(), - ); wt_client - .add_update_tower(tower1_id, &tower_info.net_addr, &receipt) + .add_update_tower(tower1_id, "talaia.watch", &receipt) .unwrap(); wt_client - .add_update_tower(tower2_id, &tower_info.net_addr, &receipt) + .add_update_tower(tower2_id, "talaia.watch", &receipt) .unwrap(); let locator = generate_random_appointment(None).locator; diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index b0aed48..eb03b75 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -27,7 +27,9 @@ class TeosCLI: def _call(self, method_name, *args): try: r = subprocess.run( - ["teos-cli", f"--datadir={self.datadir}/teos", method_name, *args], capture_output=True, text=True + ["teos-cli", f"--datadir={self.datadir}/teos", method_name, *args], + capture_output=True, + text=True, ) if r.returncode != 0: result = ValueError(f"Unknown method {method_name}") diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 732f3d4..19d633d 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -1,4 +1,5 @@ import pytest +from pyln.client import RpcError from conftest import WT_PLUGIN @@ -69,7 +70,10 @@ def test_watchtower(node_factory, bitcoind, teosd): assert l2.rpc.getappointment(tower_id, locator)["status"] == "dispute_responded" else: # Once the channel gets irrevocably resolved the tower will forget about it - assert l2.rpc.getappointment(tower_id, locator) == {"error": "Appointment not found", "error_code": 36} + assert l2.rpc.getappointment(tower_id, locator) == { + "error": "Appointment not found", + "error_code": 36, + } # Make sure the penalty outputs are in l2's wallet fund_txids = [o["txid"] for o in l2.rpc.listfunds()["outputs"]] @@ -114,7 +118,16 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): def test_auto_retry_watchtower(node_factory, bitcoind, teosd): # The plugin is set to give up on retrying straight-away so we can test this fast. l1, l2 = node_factory.line_graph( - 2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True, "watchtower-max-retry-time": 1, "watchtower-auto-retry-delay": 1}] + 2, + opts=[ + {}, + { + "plugin": WT_PLUGIN, + "allow_broken_log": True, + "watchtower-max-retry-time": 1, + "watchtower-auto-retry-delay": 1, + }, + ], ) # We need to register l2 with the tower @@ -143,7 +156,15 @@ def test_auto_retry_watchtower(node_factory, bitcoind, teosd): def test_manually_retry_watchtower(node_factory, bitcoind, teosd): # The plugin is set to give up on retrying straight-away so we can test this fast. l1, l2 = node_factory.line_graph( - 2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True, "watchtower-max-retry-time": 0}] + 2, + opts=[ + {}, + { + "plugin": WT_PLUGIN, + "allow_broken_log": True, + "watchtower-max-retry-time": 0, + }, + ], ) # We need to register l2 with the tower From 44df8bb6f38967c58dd133abe6c0d11bad9d12b8 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 19 Jan 2023 14:42:12 -0500 Subject: [PATCH 071/119] Defines Endpoint enum to avoid using hardcoded strings as endpoints Useful for both the tower API and the clients --- teos-common/src/net/http.rs | 27 ++++++ teos-common/src/{net.rs => net/mod.rs} | 2 + teos/src/api/http.rs | 116 ++++++++++++++----------- watchtower-plugin/src/main.rs | 5 +- watchtower-plugin/src/net/http.rs | 50 +++++++---- watchtower-plugin/src/retrier.rs | 21 ++--- 6 files changed, 142 insertions(+), 79 deletions(-) create mode 100644 teos-common/src/net/http.rs rename teos-common/src/{net.rs => net/mod.rs} (99%) diff --git a/teos-common/src/net/http.rs b/teos-common/src/net/http.rs new file mode 100644 index 0000000..18450f9 --- /dev/null +++ b/teos-common/src/net/http.rs @@ -0,0 +1,27 @@ +pub enum Endpoint { + Register, + AddAppointment, + GetAppointment, + GetSubscriptionInfo, +} + +impl std::fmt::Display for Endpoint { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Endpoint::Register => "register", + Endpoint::AddAppointment => "add_appointment", + Endpoint::GetAppointment => "get_appointment", + Endpoint::GetSubscriptionInfo => "get_subscription_info", + } + ) + } +} + +impl Endpoint { + pub fn path(&self) -> String { + format!("/{}", self) + } +} diff --git a/teos-common/src/net.rs b/teos-common/src/net/mod.rs similarity index 99% rename from teos-common/src/net.rs rename to teos-common/src/net/mod.rs index 2831ae2..266e0c6 100644 --- a/teos-common/src/net.rs +++ b/teos-common/src/net/mod.rs @@ -1,3 +1,5 @@ +pub mod http; + use serde::Serialize; use std::fmt; diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index aa7eb62..53da92a 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -8,6 +8,7 @@ use triggered::{Listener, Trigger}; use warp::{http::StatusCode, reject, reply, Filter, Rejection, Reply}; use teos_common::appointment::LOCATOR_LEN; +use teos_common::net::http::Endpoint; use teos_common::protos as common_msgs; use teos_common::{errors, USER_ID_LEN}; @@ -221,28 +222,28 @@ fn router( grpc_conn: PublicTowerServicesClient, ) -> impl Filter + Clone { let register = warp::post() - .and(warp::path("register")) + .and(warp::path(Endpoint::Register.to_string())) .and(warp::body::content_length_limit(REGISTER_BODY_LEN).and(warp::body::json())) .and(warp::addr::remote()) .and(with_grpc(grpc_conn.clone())) .and_then(register); let add_appointment = warp::post() - .and(warp::path("add_appointment")) + .and(warp::path(Endpoint::AddAppointment.to_string())) .and(warp::body::content_length_limit(ADD_APPOINTMENT_BODY_LEN).and(warp::body::json())) .and(warp::addr::remote()) .and(with_grpc(grpc_conn.clone())) .and_then(add_appointment); let get_appointment = warp::post() - .and(warp::path("get_appointment")) + .and(warp::path(Endpoint::GetAppointment.to_string())) .and(warp::body::content_length_limit(GET_APPOINTMENT_BODY_LEN).and(warp::body::json())) .and(warp::addr::remote()) .and(with_grpc(grpc_conn.clone())) .and_then(get_appointment); let get_subscription_info = warp::post() - .and(warp::path("get_subscription_info")) + .and(warp::path(Endpoint::GetSubscriptionInfo.to_string())) .and( warp::body::content_length_limit(GET_SUBSCRIPTION_INFO_BODY_LEN) .and(warp::body::json()), @@ -357,9 +358,9 @@ mod test_helpers { (sock_addr, bitcoind_stopper) } - pub(crate) async fn check_api_error<'a>( - endpoint: &str, - body: RequestBody<'a>, + pub(crate) async fn check_api_error( + endpoint: Endpoint, + body: RequestBody<'_>, server_addr: SocketAddr, ) -> (ApiError, StatusCode) { let grpc_conn = PublicTowerServicesClient::connect(format!( @@ -371,15 +372,22 @@ mod test_helpers { .unwrap(); let req = match body { - RequestBody::Json(j) => warp::test::request().method("POST").path(endpoint).json(&j), - RequestBody::DoNotJsonify(j) => { - warp::test::request().method("POST").path(endpoint).json(&j) - } + RequestBody::Json(j) => warp::test::request() + .method("POST") + .path(&endpoint.path()) + .json(&j), + RequestBody::DoNotJsonify(j) => warp::test::request() + .method("POST") + .path(&endpoint.path()) + .json(&j), RequestBody::Jsonify(j) => warp::test::request() .method("POST") - .path(endpoint) + .path(&endpoint.path()) .json(&serde_json::from_str::(j).unwrap()), - RequestBody::Body(b) => warp::test::request().method("POST").path(endpoint).body(b), + RequestBody::Body(b) => warp::test::request() + .method("POST") + .path(&endpoint.path()) + .body(b), }; let res = req.reply(&router(grpc_conn)).await; @@ -390,7 +398,7 @@ mod test_helpers { } pub(crate) async fn request_to_api( - endpoint: &str, + endpoint: Endpoint, body: B, server_addr: SocketAddr, ) -> Result @@ -408,7 +416,7 @@ mod test_helpers { let res = warp::test::request() .method("POST") - .path(endpoint) + .path(&endpoint.path()) .json(&serde_json::json!(body)) .reply(&router(grpc_conn)) .await; @@ -428,7 +436,7 @@ mod tests_failures { async fn test_no_json_request_body() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = - check_api_error("/register", RequestBody::Body(""), server_addr).await; + check_api_error(Endpoint::Register, RequestBody::Body(""), server_addr).await; assert!(api_error.error.contains("EOF while parsing")); assert_eq!(api_error.error_code, errors::INVALID_REQUEST_FORMAT); assert_eq!(status, StatusCode::BAD_REQUEST); @@ -437,8 +445,12 @@ mod tests_failures { #[tokio::test] async fn test_wrong_json_request_body() { let (server_addr, _s) = run_tower_in_background().await; - let (api_error, status) = - check_api_error("/register", RequestBody::DoNotJsonify(""), server_addr).await; + let (api_error, status) = check_api_error( + Endpoint::Register, + RequestBody::DoNotJsonify(""), + server_addr, + ) + .await; assert!(api_error.error.contains("expected struct")); assert_eq!(api_error.error_code, errors::WRONG_FIELD_TYPE); assert_eq!(status, StatusCode::BAD_REQUEST); @@ -447,8 +459,12 @@ mod tests_failures { #[tokio::test] async fn test_empty_json_request_body() { let (server_addr, _s) = run_tower_in_background().await; - let (api_error, status) = - check_api_error("/register", RequestBody::Jsonify(r#"{}"#), server_addr).await; + let (api_error, status) = check_api_error( + Endpoint::Register, + RequestBody::Jsonify(r#"{}"#), + server_addr, + ) + .await; assert!(api_error.error.contains("missing field")); assert_eq!(api_error.error_code, errors::MISSING_FIELD); assert_eq!(status, StatusCode::BAD_REQUEST); @@ -458,7 +474,7 @@ mod tests_failures { async fn test_empty_field() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( - "/register", + Endpoint::Register, RequestBody::Jsonify(r#"{"user_id": ""}"#), server_addr, ) @@ -472,7 +488,7 @@ mod tests_failures { async fn test_wrong_field_hex_encoding_odd() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( - "/register", + Endpoint::Register, RequestBody::Jsonify(r#"{"user_id": "a"}"#), server_addr, ) @@ -486,7 +502,7 @@ mod tests_failures { async fn test_wrong_hex_encoding_character() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = - check_api_error("/register", + check_api_error(Endpoint::Register, RequestBody::Jsonify(r#"{"user_id": "022fa2900ed7fc07b4e8ca3ea081e846245b0497944644aa78ea0b994ac22074dZ"}"#), server_addr ).await; @@ -500,7 +516,7 @@ mod tests_failures { async fn test_wrong_field_size() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( - "/register", + Endpoint::Register, RequestBody::Jsonify(r#"{"user_id": "aa"}"#), server_addr, ) @@ -515,7 +531,7 @@ mod tests_failures { async fn test_wrong_field_type() { let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( - "/register", + Endpoint::Register, RequestBody::DoNotJsonify(r#"{"user_id": 1}"#), server_addr, ) @@ -530,7 +546,7 @@ mod tests_failures { // We'll use a different endpoint here since we need a json object with more than one field let (server_addr, _s) = run_tower_in_background().await; let (api_error, status) = check_api_error( - "/add_appointment", + Endpoint::AddAppointment, RequestBody::Jsonify(r#"{"signature": "aa"}"#), server_addr, ) @@ -555,7 +571,7 @@ mod tests_failures { let res = warp::test::request() .method("POST") - .path("/register") + .path(&Endpoint::Register.path()) .reply(&router(grpc_conn)) .await; @@ -575,7 +591,7 @@ mod tests_failures { let res = warp::test::request() .method("POST") - .path("/register") + .path(&Endpoint::Register.path()) .json(&format!("{}{}", get_random_user_id(), get_random_user_id())) .reply(&router(grpc_conn)) .await; @@ -596,7 +612,6 @@ mod tests_failures { let res = warp::test::request() .method("POST") - .path("/") .json(&"") .reply(&router(grpc_conn)) .await; @@ -616,7 +631,6 @@ mod tests_failures { .unwrap(); let res = warp::test::request() - .path("/") .json(&"") .reply(&router(grpc_conn)) .await; @@ -644,7 +658,7 @@ mod tests_methods { let (server_addr, _s) = run_tower_in_background().await; let response = request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: get_random_user_id().to_vec(), }, @@ -662,7 +676,7 @@ mod tests_methods { // Register once, this should go trough and set slots to the limit request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_id.to_vec(), }, @@ -674,7 +688,7 @@ mod tests_methods { // Register again to get additional slots, this should fail assert_eq!( check_api_error( - "/register", + Endpoint::Register, RequestBody::Json(serde_json::json!(common_msgs::RegisterRequest { user_id: user_id.to_vec(), })), @@ -702,7 +716,7 @@ mod tests_methods { // Register with bitcoind down assert_eq!( check_api_error( - "/register", + Endpoint::Register, RequestBody::Json(serde_json::json!(common_msgs::RegisterRequest { user_id: user_id.to_vec(), })), @@ -726,7 +740,7 @@ mod tests_methods { // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_pk.serialize().to_vec(), }, @@ -743,7 +757,7 @@ mod tests_methods { common_msgs::AddAppointmentRequest, common_msgs::AddAppointmentResponse, >( - "/add_appointment", + Endpoint::AddAppointment, common_msgs::AddAppointmentRequest { appointment: Some(appointment.into()), signature, @@ -767,7 +781,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/add_appointment", + Endpoint::AddAppointment, RequestBody::Json(serde_json::json!(common_msgs::AddAppointmentRequest { appointment: Some(appointment.into()), signature, @@ -794,7 +808,7 @@ mod tests_methods { // Register let (user_sk, user_pk) = cryptography::get_random_keypair(); request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_pk.serialize().to_vec(), }, @@ -813,7 +827,7 @@ mod tests_methods { // Try to add it via the http API assert_eq!( check_api_error( - "/add_appointment", + Endpoint::AddAppointment, RequestBody::Json(serde_json::json!(common_msgs::AddAppointmentRequest { appointment: Some(appointment.into()), signature, @@ -843,7 +857,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/add_appointment", + Endpoint::AddAppointment, RequestBody::Json(serde_json::json!(common_msgs::AddAppointmentRequest { appointment: Some(appointment.into()), signature, @@ -868,7 +882,7 @@ mod tests_methods { // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_pk.serialize().to_vec(), }, @@ -882,7 +896,7 @@ mod tests_methods { let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); request_to_api::( - "/add_appointment", + Endpoint::AddAppointment, common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), signature, @@ -897,7 +911,7 @@ mod tests_methods { common_msgs::GetAppointmentRequest, common_msgs::GetAppointmentResponse, >( - "/get_appointment", + Endpoint::GetAppointment, common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), signature: cryptography::sign( @@ -927,7 +941,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/get_appointment", + Endpoint::GetAppointment, RequestBody::Json(serde_json::json!(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), signature: cryptography::sign( @@ -956,7 +970,7 @@ mod tests_methods { // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_pk.serialize().to_vec(), }, @@ -970,7 +984,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/get_appointment", + Endpoint::GetAppointment, RequestBody::Json(serde_json::json!(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), signature: cryptography::sign( @@ -1005,7 +1019,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/get_appointment", + Endpoint::GetAppointment, RequestBody::Json(serde_json::json!(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), signature: cryptography::sign( @@ -1034,7 +1048,7 @@ mod tests_methods { // Register first let (user_sk, user_pk) = cryptography::get_random_keypair(); request_to_api::( - "/register", + Endpoint::Register, common_msgs::RegisterRequest { user_id: user_pk.serialize().to_vec(), }, @@ -1048,7 +1062,7 @@ mod tests_methods { common_msgs::GetSubscriptionInfoRequest, common_msgs::GetSubscriptionInfoResponse, >( - "/get_subscription_info", + Endpoint::GetSubscriptionInfo, common_msgs::GetSubscriptionInfoRequest { signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) .unwrap(), @@ -1072,7 +1086,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/get_subscription_info", + Endpoint::GetSubscriptionInfo, RequestBody::Json(serde_json::json!(common_msgs::GetSubscriptionInfoRequest { signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) .unwrap(), @@ -1100,7 +1114,7 @@ mod tests_methods { assert_eq!( check_api_error( - "/get_subscription_info", + Endpoint::GetSubscriptionInfo, RequestBody::Json(serde_json::json!(common_msgs::GetSubscriptionInfoRequest { signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) .unwrap(), diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 0f96e7d..1e48a07 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -12,6 +12,7 @@ use cln_plugin::options::{ConfigOption, Value}; use cln_plugin::{anyhow, Builder, Error, Plugin}; use teos_common::appointment::{Appointment, Locator}; +use teos_common::net::http::Endpoint; use teos_common::net::NetAddr; use teos_common::protos as common_msgs; use teos_common::TowerId; @@ -170,7 +171,7 @@ async fn get_subscription_info( let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( post_request( &tower_net_addr, - "get_subscription_info", + Endpoint::GetSubscriptionInfo, &common_msgs::GetSubscriptionInfoRequest { signature }, &proxy, ) @@ -216,7 +217,7 @@ async fn get_appointment( let response: ApiResponse = process_post_response( post_request( &tower_net_addr, - "get_appointment", + Endpoint::GetAppointment, &common_msgs::GetAppointmentRequest { locator: params.locator.to_vec(), signature, diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 2d7b187..fc063a0 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -3,6 +3,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use teos_common::appointment::Appointment; use teos_common::cryptography; +use teos_common::net::http::Endpoint; use teos_common::net::NetAddr; use teos_common::protos as common_msgs; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; @@ -65,7 +66,7 @@ pub async fn register( process_post_response( post_request( tower_net_addr, - "register", + Endpoint::Register, &common_msgs::RegisterRequest { user_id: user_id.to_vec(), }, @@ -121,7 +122,13 @@ pub async fn send_appointment( }; match process_post_response( - post_request(tower_net_addr, "add_appointment", &request_data, proxy).await, + post_request( + tower_net_addr, + Endpoint::AddAppointment, + &request_data, + proxy, + ) + .await, ) .await? { @@ -151,7 +158,7 @@ pub async fn send_appointment( /// Generic function to post different types of requests to the tower. pub async fn post_request( tower_net_addr: &NetAddr, - endpoint: &str, + endpoint: Endpoint, data: S, proxy: &Option, ) -> Result { @@ -178,7 +185,7 @@ pub async fn post_request( }; client - .post(format!("{}/{}", tower_net_addr.net_addr(), endpoint)) + .post(format!("{}{}", tower_net_addr.net_addr(), endpoint.path())) .json(&data) .send() .await @@ -249,7 +256,7 @@ mod tests { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/register"); + when.method(POST).path(Endpoint::Register.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(registration_receipt)); @@ -286,7 +293,7 @@ mod tests { async fn test_register_deserialize_error() { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/register"); + when.method(POST).path(Endpoint::Register.path()); then.status(200) .header("content-type", "application/json") .json_body(json!([])); @@ -318,7 +325,7 @@ mod tests { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -350,7 +357,7 @@ mod tests { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -382,7 +389,7 @@ mod tests { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -437,7 +444,7 @@ mod tests { async fn test_send_appointment_deserialize_error() { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!([])); @@ -470,7 +477,7 @@ mod tests { let server = MockServer::start(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(400) .header("content-type", "application/json") .json_body(json!(api_error)); @@ -498,9 +505,14 @@ mod tests { then.status(200).header("content-type", "application/json"); }); - let response = post_request(&NetAddr::new(server.base_url()), "", json!(""), &None) - .await - .unwrap(); + let response = post_request( + &NetAddr::new(server.base_url()), + Endpoint::Register, + json!(""), + &None, + ) + .await + .unwrap(); api_mock.assert(); assert!(matches!(response, Response { .. })); @@ -511,7 +523,7 @@ mod tests { assert!(matches!( post_request( &NetAddr::new("http://unreachable_url".to_owned()), - "", + Endpoint::Register, json!(""), &None, ) @@ -533,7 +545,13 @@ mod tests { // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( - post_request(&NetAddr::new(server.base_url()), "", json!(""), &None).await, + post_request( + &NetAddr::new(server.base_url()), + Endpoint::GetAppointment, + json!(""), + &None, + ) + .await, ) .await .unwrap_err(); diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 8f7c1ba..417ec18 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -586,6 +586,7 @@ mod tests { use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; + use teos_common::net::http::Endpoint; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, @@ -649,7 +650,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") @@ -784,7 +785,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -855,7 +856,7 @@ mod tests { // Prepare the mock response let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(400) .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") @@ -956,7 +957,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") @@ -1082,7 +1083,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let add_appointment_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") @@ -1229,7 +1230,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -1315,7 +1316,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -1385,7 +1386,7 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); @@ -1451,7 +1452,7 @@ mod tests { .unwrap(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(400) .header("content-type", "application/json") .json_body(json!(ApiError { @@ -1500,7 +1501,7 @@ mod tests { .unwrap(); let api_mock = server.mock(|when, then| { - when.method(POST).path("/add_appointment"); + when.method(POST).path(Endpoint::AddAppointment.path()); then.status(400) .header("content-type", "application/json") .json_body(json!(ApiError { From 197ae93534d196ee0ea3511575ec8cd2c49f15b2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 20 Jan 2023 14:42:20 -0500 Subject: [PATCH 072/119] Updates readmes --- README.md | 4 ++-- watchtower-plugin/README.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fa7d4b0..4213e2d 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ For regtest, it should look like: btc_network = regtest ``` -### Running `teosd` with tor +### Running `teosd` with Tor This requires a Tor daemon running on the same machine as `teosd` and a control port open on that daemon. Download Tor from the [torproject site](https://www.torproject.org/download/). -To open tor's control port, you add the following to the Tor config file ([source](https://2019.www.torproject.org/docs/faq.html.en#torrc)): +To open Tor's control port, you add the following to the Tor config file ([source](https://2019.www.torproject.org/docs/faq.html.en#torrc)): ``` ## The port on which Tor will listen for local connections from Tor diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index f0738d9..bff4727 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -109,9 +109,10 @@ Config options can be setup directly in the [CLN config file](https://github.com - `watchtower-port`: default tower API port. - `watchtower-max-retry-time`: the maximum time a retry strategy will try to reach a temporary unreachable tower before giving up. -- `watchtower-proxy`: informs the plugin that you have a SOCKS5 proxy at the given `ip:port`. Notice this is necessary if you want to connect to a tower through Tor! +- `proxy`: Set a socks v5 proxy IP address and port. Notice this is necessary if you want to connect to a tower through Tor! +- `always-use-proxy`: Use the proxy always (default: false). -**DISCLAIMER**: This option will be eventually replaced by the CoreLN `proxy` / `always-use-proxy` options. In the current state of the `cln-plugin` crate there is no option to access the CoreLN main configuration, therefore the need for a temporary, plugin-specific, option. +Notice `proxy` and `always-use-proxy` are general CLN options that are honored by the plugin, so if set the plugin will use Tor to communicate with the tower. # Getting started From f18237bb3ea2776da304196c2050b7dbe50af79d Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 23 Jan 2023 11:20:41 -0500 Subject: [PATCH 073/119] Remove unused defaults, document new ones and updates defaults - `watchtower-proxy` was still part of constants but it was not being used anymore - `watchtower-auto-retry-delay` was not part of the README Also redefines defaults so a retry strategy lasts at most 1h, every interval is at most 15 minutes and automatic retries are triggered every 8 hours. --- watchtower-plugin/README.md | 5 +++-- watchtower-plugin/src/constants.rs | 14 ++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index bff4727..1810653 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -108,8 +108,9 @@ All the appointments generated by the tower, as well as all the registered tower Config options can be setup directly in the [CLN config file](https://github.com/ElementsProject/lightning#configuration-file). The currently available options are: - `watchtower-port`: default tower API port. -- `watchtower-max-retry-time`: the maximum time a retry strategy will try to reach a temporary unreachable tower before giving up. -- `proxy`: Set a socks v5 proxy IP address and port. Notice this is necessary if you want to connect to a tower through Tor! +- `watchtower-max-retry-time`: for how long (in seconds) a retry strategy will try to reach a temporary unreachable tower before giving up (default: 1 hour). +- `watchtower-auto-retry-delay`: how long (in seconds) the client will wait before auto-retrying a failed tower (default: 8 hours). +- `proxy`: Set a socks v5 proxy IP address and port. Notice this is necessary if you want to connect to a tower through Tor! (default: no proxy). - `always-use-proxy`: Use the proxy always (default: false). Notice `proxy` and `always-use-proxy` are general CLN options that are honored by the plugin, so if set the plugin will use Tor to communicate with the tower. diff --git a/watchtower-plugin/src/constants.rs b/watchtower-plugin/src/constants.rs index b450cd3..1b14803 100644 --- a/watchtower-plugin/src/constants.rs +++ b/watchtower-plugin/src/constants.rs @@ -8,17 +8,15 @@ pub const WT_PORT: &str = "watchtower-port"; pub const DEFAULT_WT_PORT: i64 = 9814; pub const WT_PORT_DESC: &str = "tower API port"; pub const WT_MAX_RETRY_TIME: &str = "watchtower-max-retry-time"; -pub const DEFAULT_WT_MAX_RETRY_TIME: i64 = 900; -pub const WT_MAX_RETRY_TIME_DESC: &str = "the time (in seconds) after when a retrier will give up trying to send data to a temporary unreachable tower"; -pub const WT_PROXY: &str = "watchtower-proxy"; -pub const WT_PROXY_DESC: &str = "Socks v5 proxy IP address and port for the watchtower client"; +pub const DEFAULT_WT_MAX_RETRY_TIME: i64 = 3600; +pub const WT_MAX_RETRY_TIME_DESC: &str = "for how long (in seconds) a retry strategy will try to reach a temporary unreachable tower before giving up. Defaults to 1 hour"; pub const WT_AUTO_RETRY_DELAY: &str = "watchtower-auto-retry-delay"; -pub const DEFAULT_WT_AUTO_RETRY_DELAY: i64 = 86400; -pub const WT_AUTO_RETRY_DELAY_DESC: &str = "the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day"; +pub const DEFAULT_WT_AUTO_RETRY_DELAY: i64 = 28800; +pub const WT_AUTO_RETRY_DELAY_DESC: &str = "how long (in seconds) a retrier will wait before auto-retrying a failed tower. Defaults to once every 8 hours"; pub const DEV_WT_MAX_RETRY_INTERVAL: &str = "dev-watchtower-max-retry-interval"; -pub const DEFAULT_DEV_WT_MAX_RETRY_INTERVAL: i64 = 60; +pub const DEFAULT_DEV_WT_MAX_RETRY_INTERVAL: i64 = 900; pub const DEV_WT_MAX_RETRY_INTERVAL_DESC: &str = - "the maximum time (in seconds) for a retrier wait interval"; + "maximum length (in seconds) for a retry interval. Defaults to 15 min"; /// Collections of rpc method names and descriptions From eda7b263e621587ca1ffbbcdf724b13e110dd673 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 20 Jan 2023 16:06:30 -0500 Subject: [PATCH 074/119] Adds teosd.service to run teos as daemon This builds on top of the work from the work by @decentralizedb and @jochemin. It is mainly just re-arranging the code and readmes to fit the reviews. Co-authored-by: decentralizedb Co-authored-by: jochemin --- INSTALL.md | 6 ++++- contrib/init/README.md | 42 +++++++++++++++++++++++++++++++++++ contrib/init/teosd.service | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 contrib/init/README.md create mode 100644 contrib/init/teosd.service diff --git a/INSTALL.md b/INSTALL.md index 35d422f..bb32bda 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,4 +14,8 @@ You can run tests with: cargo test ``` -Please refer to the cargo documentation for more detailed instructions. \ No newline at end of file +Please refer to the cargo documentation for more detailed instructions. + +# Systemd setup for backend + +Refer to [contrib](contrib/init/README.md) for a detailed explanation of how to set up your systemd service for `teosd`. \ No newline at end of file diff --git a/contrib/init/README.md b/contrib/init/README.md new file mode 100644 index 0000000..e627707 --- /dev/null +++ b/contrib/init/README.md @@ -0,0 +1,42 @@ +**This document guides you into how to set-up a systemd service to run `teosd`.** + +Since the teos service requires bitcoin to run, it is strongly recommended to also create a [system service for bitcoin](https://github.com/bitcoin/bitcoin/blob/master/contrib/init/bitcoind.service). + +Once you have set the bitcoin service, proceed to copy [teosd.service](teosd.service) to the systemd folder, that is, if running from this folder: + +``` +cp teosd.service /etc/systemd/system +``` + +You can also create a file called `teosd.service` in the systemd folder and copy the content of [teosd.service](teosd.service) to it: + +``` +sudo vim /etc/systemd/system/teosd.service +``` + +Notice the provided service file is using `teos` both as user and group for the service, so you may want to update that if that is not the configuration you are intending to use. Here are the lines to be updated: + +``` +[Service] +ExecStart=/home//.cargo/bin/teosd +SyslogIdentifier= + +# Directory creation and permissions +#################################### +User= +Group= +``` + +The next step is enabling the service. You can do so by running: + +``` +sudo systemctl enable teosd.service +``` + +Finally, you can start the service by running: + +``` +sudo systemctl start teosd.service +``` + +From that point on, the tower will be run every time your system is turned on, and restarted if needed. diff --git a/contrib/init/teosd.service b/contrib/init/teosd.service new file mode 100644 index 0000000..2fef183 --- /dev/null +++ b/contrib/init/teosd.service @@ -0,0 +1,45 @@ +[Unit] +Description=The Eye of Satoshi daemon +After=bitcoind.service network.target +Requires=bitcoind.service +Wants=network.target + +[Service] +ExecStart=/home/teos/.cargo/bin/teosd +StandardOutput=journal +StandardError=journal +SyslogIdentifier=teos + +# Process management +#################### +Type=simple +Restart=on-failure +TimeoutSec=300 +RestartSec=60 + +# Directory creation and permissions +#################################### +User=teos +Group=teos + +# Hardening measures +#################### +# Provide a private /tmp and /var/tmp. +PrivateTmp=true + +# Mount /usr, /boot/ and /etc read-only for the process. +ProtectSystem=full + +# Disallow the process and all of its children to gain +# new privileges through execve(). +NoNewPrivileges=true + +# Use a new /dev namespace only populated with API pseudo devices +# such as /dev/null, /dev/zero and /dev/random. +PrivateDevices=true + +# Deny the creation of writable and executable memory mappings. +MemoryDenyWriteExecute=true + +[Install] +WantedBy=multi-user.target \ No newline at end of file From 72b1805c1b0c5dae201fd7a70b26b7f2801b1115 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 23 Jan 2023 09:27:15 -0500 Subject: [PATCH 075/119] Adds logs to RPC requests and improves the ones for the http interface Also updates the severity from the http logs from info to debug --- teos/src/api/http.rs | 36 +++++++++++++-------------- teos/src/api/internal.rs | 53 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 53da92a..f35b9b2 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -98,13 +98,13 @@ fn parse_grpc_response( match result { Ok(r) => { let inner = r.into_inner(); - log::info!("Request succeeded"); + log::debug!("Request succeeded"); log::debug!("Response: {}", serde_json::json!(inner)); (reply::json(&inner), StatusCode::OK) } Err(s) => { let (status_code, error_code) = match_status(&s); - log::info!("Request failed, error_code={}", error_code); + log::debug!("Request failed, error_code={}", error_code); log::debug!("Response: {}", serde_json::json!(s.message())); ( reply::json(&ApiError::new(s.message().into(), error_code)), @@ -119,10 +119,10 @@ async fn register( addr: Option, mut grpc_conn: PublicTowerServicesClient, ) -> std::result::Result { - match addr { - Some(a) => log::info!("Received register request from {}", a), - None => log::info!("Received register request from unknown address"), - } + log::debug!( + "Received a register request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); let user_id = req.user_id.clone(); if user_id.is_empty() { @@ -145,10 +145,10 @@ async fn add_appointment( addr: Option, mut grpc_conn: PublicTowerServicesClient, ) -> std::result::Result { - match addr { - Some(a) => log::info!("Received add_appointment request from {}", a), - None => log::info!("Received add_appointment request from unknown address"), - } + log::debug!( + "Received an add_appointment request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); if let Some(a) = &req.appointment { if a.locator.is_empty() { @@ -177,10 +177,10 @@ async fn get_appointment( addr: Option, mut grpc_conn: PublicTowerServicesClient, ) -> std::result::Result { - match addr { - Some(a) => log::info!("Received get_appointment request from {}", a), - None => log::info!("Received get_appointment request from unknown address"), - } + log::debug!( + "Received an get_appointment request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); if req.locator.is_empty() { return Err(ApiError::empty_field("locator")); @@ -205,10 +205,10 @@ async fn get_subscription_info( addr: Option, mut grpc_conn: PublicTowerServicesClient, ) -> std::result::Result { - match addr { - Some(a) => log::info!("Received get_subscription_info request from {}", a), - None => log::info!("Received get_subscription_info request from unknown address"), - } + log::debug!( + "Received an get_subscription_info request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); if req.signature.is_empty() { return Err(ApiError::empty_field("signature")); diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index 0edcde3..2fda02f 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -232,8 +232,15 @@ impl PrivateTowerServices for Arc { /// Internally calls [Watcher::get_all_watcher_appointments] and [Watcher::get_all_responder_trackers]. async fn get_all_appointments( &self, - _: Request<()>, + request: Request<()>, ) -> Result, Status> { + log::debug!( + "Received a get_all_appointments request from {}", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + let mut all_appointments = Vec::new(); for (_, appointment) in self.watcher.get_all_watcher_appointments().into_iter() { @@ -265,6 +272,13 @@ impl PrivateTowerServices for Arc { &self, request: tonic::Request, ) -> Result, Status> { + log::debug!( + "Received a get_appointments requests from {}", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + let mut matching_appointments = vec![]; let locator = Locator::from_slice(&request.into_inner().locator).map_err(|_| { Status::new( @@ -309,8 +323,15 @@ impl PrivateTowerServices for Arc { /// and [Watcher::get_trackers_count]. async fn get_tower_info( &self, - _: Request<()>, + request: Request<()>, ) -> Result, Status> { + log::debug!( + "Received a get_tower_info request from {}", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + Ok(Response::new(msgs::GetTowerInfoResponse { tower_id: self.watcher.tower_id.to_vec(), addresses: self.get_addresses().clone(), @@ -323,7 +344,17 @@ impl PrivateTowerServices for Arc { /// Get user endpoint. Gets all users in the tower. Part of the private API. /// Internally calls [Watcher::get_user_ids]. - async fn get_users(&self, _: Request<()>) -> Result, Status> { + async fn get_users( + &self, + request: Request<()>, + ) -> Result, Status> { + log::debug!( + "Received a get_users requests from {}", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + let user_ids = self .watcher .get_user_ids() @@ -340,6 +371,13 @@ impl PrivateTowerServices for Arc { &self, request: Request, ) -> Result, Status> { + log::debug!( + "Received a get_user request from {}", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + let user_id = UserId::from_slice(&request.into_inner().user_id).map_err(|_| { Status::new( Code::InvalidArgument, @@ -358,10 +396,15 @@ impl PrivateTowerServices for Arc { } /// Stop endpoint. Stops the tower daemon. Part of the private API. - async fn stop(&self, _: Request<()>) -> Result, Status> { + async fn stop(&self, request: Request<()>) -> Result, Status> { self.shutdown_trigger.trigger(); - log::debug!("Received shutting down signal, notifying components"); + log::debug!( + "Received a shutting down request from {}, notifying components", + request + .remote_addr() + .map_or("an unknown address".to_owned(), |a| a.to_string()) + ); Ok(Response::new(())) } } From 5cf9029fe9c43d557d92caf4c080b42d8ee29ed8 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 23 Jan 2023 13:18:34 -0500 Subject: [PATCH 076/119] Bumps teos version to 0.2 in preparation for code release --- Cargo.lock | 6 +++--- teos-common/Cargo.toml | 2 +- teos/Cargo.toml | 2 +- watchtower-plugin/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e28124a..0a9edf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2979,7 +2979,7 @@ dependencies = [ [[package]] name = "teos" -version = "0.1.2" +version = "0.2.0" dependencies = [ "bitcoin", "bitcoincore-rpc", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "teos-common" -version = "0.1.2" +version = "0.2.0" dependencies = [ "bitcoin", "chacha20poly1305", @@ -3744,7 +3744,7 @@ checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "watchtower-plugin" -version = "0.1.2" +version = "0.2.0" dependencies = [ "backoff", "bitcoin", diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 0ee03cf..be0cb48 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teos-common" -version = "0.1.2" +version = "0.2.0" authors = ["Sergi Delgado Segura "] edition = "2018" diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 4f25c94..b062b46 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teos" -version = "0.1.2" +version = "0.2.0" authors = ["Sergi Delgado Segura "] license = "MIT" edition = "2018" diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 20504a3..199cb8f 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "watchtower-plugin" -version = "0.1.2" +version = "0.2.0" authors = ["Sergi Delgado Segura "] license = "MIT" edition = "2018" From 73a9bff9fa5d5bf7ac82c790881ae81d3eee0eed Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 26 Jan 2023 12:58:16 -0500 Subject: [PATCH 077/119] Defines Retrier polling waiting time as a constant and fixes tests The pooling time for the Retrier was hardcoded to 1, let's at least use a constant for that. Also, `retrier::tests::test_manage_retry_while_idle` was randomly failing (for Ubuntu) when checking whether the Retrier was idle after giving up on a retry. This is due to the time of running a round not being taken into account. --- watchtower-plugin/src/retrier.rs | 111 ++++++++++++++++++------------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 417ec18..a66416a 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -16,6 +16,8 @@ use crate::net::http::{self, AddAppointmentError}; use crate::wt_client::{RevocationData, WTClient}; use crate::{MisbehaviorProof, TowerStatus}; +const POLLING_TIME: u64 = 1; + #[derive(Eq, PartialEq, Debug)] enum RetryError { // bool marks whether the Subscription error is permanent or not @@ -167,7 +169,7 @@ impl RetryManager { } } // Sleep to not waste a lot of CPU cycles. - tokio::time::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(POLLING_TIME)).await; } Err(TryRecvError::Disconnected) => break, } @@ -599,8 +601,10 @@ mod tests { const LONG_AUTO_RETRY_DELAY: u32 = 60; const SHORT_AUTO_RETRY_DELAY: u32 = 3; const API_DELAY: f64 = 0.5; + const HALF_API_DELAY: f64 = API_DELAY / 2.0; const MAX_ELAPSED_TIME: u16 = 2; const MAX_INTERVAL_TIME: u16 = 1; + const MAX_RUN_TIME: f64 = 0.2; impl Retrier { fn empty(wt_client: Arc>, tower_id: TowerId) -> Self { @@ -658,6 +662,9 @@ mod tests { }); // Start the task and send the tower to the channel for retry + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); + let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { RetryManager::new( @@ -670,18 +677,18 @@ mod tests { .manage_retry() .await }); - tx.send((tower_id, RevocationData::Fresh(appointment.locator))) - .unwrap(); - // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs((API_DELAY / 2.0) as u64)).await; + // Wait for a fraction of the API delay and check how the tower status changed + tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY)).await; assert!(wt_client .lock() .unwrap() .get_retrier_status(&tower_id) .unwrap() .is_running()); - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + + // Wait for the remaining time and re-check + tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; let state = wt_client.lock().unwrap(); assert_eq!( @@ -727,24 +734,24 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Start the task and send the tower to the channel for retry - let wt_client_clone = wt_client.clone(); - - let mut retry_manager = RetryManager::new( - wt_client_clone, - rx, - MAX_ELAPSED_TIME + 1, - SHORT_AUTO_RETRY_DELAY, - MAX_INTERVAL_TIME, - ); - let task = tokio::spawn(async move { retry_manager.manage_retry().await }); tx.send((tower_id, RevocationData::Fresh(appointment.locator))) .unwrap(); - // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs_f64( - (MAX_ELAPSED_TIME as f64 + 1.0) / 2.0, - )) - .await; + let wt_client_clone = wt_client.clone(); + let task = tokio::spawn(async move { + RetryManager::new( + wt_client_clone, + rx, + MAX_ELAPSED_TIME, + SHORT_AUTO_RETRY_DELAY, + MAX_INTERVAL_TIME, + ) + .manage_retry() + .await + }); + + // Wait for one retry round and check to tower status + tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME)).await; assert!(wt_client .lock() .unwrap() @@ -758,8 +765,9 @@ mod tests { .unwrap() .is_running()); - // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, - // so the retiers will be idle). + // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, so the retiers will be idle). + // Notice we'd normally wait for MAX_ELAPSED_TIME + MAX_RUN_TIME (the maximum time a Retrier can be working plus the marginal time of the last retry). + // However, we've already waited for MAX_RUN_TIME right before to check the tower was temporary unreachable, so we don't need to account for that again. tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; assert!(wt_client .lock() @@ -867,6 +875,9 @@ mod tests { }); // Start the task and send the tower to the channel for retry + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); + let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { RetryManager::new( @@ -879,10 +890,9 @@ mod tests { .manage_retry() .await }); - tx.send((tower_id, RevocationData::Fresh(appointment.locator))) - .unwrap(); - // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs((API_DELAY / 2.0) as u64)).await; + + // Wait for a fraction of the API delay and check how the tower status changed + tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY)).await; assert!(wt_client .lock() .unwrap() @@ -890,7 +900,8 @@ mod tests { .unwrap() .is_running()); - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + // Wait for the remaining time and re-check + tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; assert_eq!( wt_client .lock() @@ -965,6 +976,9 @@ mod tests { }); // Start the task and send the tower to the channel for retry + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); + let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { RetryManager::new( @@ -977,11 +991,9 @@ mod tests { .manage_retry() .await }); - tx.send((tower_id, RevocationData::Fresh(appointment.locator))) - .unwrap(); - // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs_f64(API_DELAY / 2.0)).await; + // Wait for a fraction of the API delay and check how the tower status changed + tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY)).await; assert!(wt_client .lock() .unwrap() @@ -989,13 +1001,17 @@ mod tests { .unwrap() .is_running()); - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + // Wait for the remaining time and re-check + tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY + MAX_RUN_TIME)).await; assert!(wt_client .lock() .unwrap() .get_tower_status(&tower_id) .unwrap() .is_misbehaving()); + + // Retriers are wiped every polling interval, so we'll need to wait a bit more to check it + tokio::time::sleep(Duration::from_secs(POLLING_TIME)).await; assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); api_mock.assert(); @@ -1025,6 +1041,8 @@ mod tests { wt_client.lock().unwrap().remove_tower(tower_id).unwrap(); // Start the task and send the tower to the channel for retry + tx.send((tower_id, RevocationData::None)).unwrap(); + let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { RetryManager::new( @@ -1038,9 +1056,6 @@ mod tests { .await }); - // Send a retry request and check how the tower is removed - tx.send((tower_id, RevocationData::None)).unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); task.abort(); @@ -1085,7 +1100,6 @@ mod tests { let add_appointment_mock = server.mock(|when, then| { when.method(POST).path(Endpoint::AddAppointment.path()); then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") .json_body(json!(add_appointment_response)); }); @@ -1095,7 +1109,7 @@ mod tests { get_registration_receipt_from_previous(®istration_receipt); re_registration_receipt.sign(&tower_sk); let register_mock = server.mock(|when, then| { - when.method(POST).path("/register"); + when.method(POST).path(Endpoint::Register.path()); then.status(200) .delay(Duration::from_secs_f64(API_DELAY)) .header("content-type", "application/json") @@ -1109,6 +1123,9 @@ mod tests { .set_tower_status(tower_id, TowerStatus::SubscriptionError); // Start the task and send the tower to the channel for retry + tx.send((tower_id, RevocationData::Fresh(appointment.locator))) + .unwrap(); + let wt_client_clone = wt_client.clone(); let task = tokio::spawn(async move { RetryManager::new( @@ -1121,10 +1138,9 @@ mod tests { .manage_retry() .await }); - tx.send((tower_id, RevocationData::Fresh(appointment.locator))) - .unwrap(); - tokio::time::sleep(Duration::from_secs_f64(API_DELAY / 2.0)).await; + // Wait for a fraction of the API delay and check how the tower status changed + tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY)).await; assert!(wt_client .lock() .unwrap() @@ -1132,8 +1148,8 @@ mod tests { .unwrap() .is_running()); - // Wait for the elapsed time and check how the tower status changed - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + // Wait for the remaining time and re-check + tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; let state = wt_client.lock().unwrap(); assert!(!state.retriers.contains_key(&tower_id)); @@ -1192,7 +1208,10 @@ mod tests { { // After the retriers gives up, it should go idling and flag the tower as unreachable - tokio::time::sleep(Duration::from_secs((MAX_ELAPSED_TIME) as u64)).await; + tokio::time::sleep(Duration::from_secs_f64( + MAX_ELAPSED_TIME as f64 + MAX_RUN_TIME, + )) + .await; let state = wt_client.lock().unwrap(); assert!(state.get_retrier_status(&tower_id).unwrap().is_idle()); @@ -1212,7 +1231,7 @@ mod tests { .unwrap(); { - tokio::time::sleep(Duration::from_secs(2)).await; + tokio::time::sleep(Duration::from_secs_f64(POLLING_TIME as f64 + MAX_RUN_TIME)).await; let state = wt_client.lock().unwrap(); assert!(state.get_retrier_status(&tower_id).unwrap().is_idle()); let tower = state.towers.get(&tower_id).unwrap(); @@ -1261,7 +1280,7 @@ mod tests { // Send a retry flag to the retrier to force a retry. tx.send((tower_id, RevocationData::None)).unwrap(); - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; + tokio::time::sleep(Duration::from_secs_f64(POLLING_TIME as f64 + MAX_RUN_TIME)).await; // FIXME: Here we should be able to check this, however, due to httpmock limitations, we cannot return a response based on the request. // Therefore, both requests will be responded with the same data. Given pending_appointments is a HashSet, we cannot even know which request // will be sent first (sets are initialized with a random state, which decided the order or iteration). From 403044298f9564f3b821e23cbf0cdbe46b8373ef Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 25 Jan 2023 16:28:37 -0500 Subject: [PATCH 078/119] Updates teosd.service, adds tor link --- contrib/init/teosd.service | 5 +++-- teos/src/main.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/init/teosd.service b/contrib/init/teosd.service index 2fef183..2a37ccd 100644 --- a/contrib/init/teosd.service +++ b/contrib/init/teosd.service @@ -1,8 +1,9 @@ [Unit] Description=The Eye of Satoshi daemon -After=bitcoind.service network.target Requires=bitcoind.service -Wants=network.target +After=bitcoind.service +Wants=network-online.target +After=network-online.target [Service] ExecStart=/home/teos/.cargo/bin/teosd diff --git a/teos/src/main.rs b/teos/src/main.rs index 1986310..023eb28 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -158,7 +158,7 @@ async fn main() { _ => e.to_string(), }; log::error!("Failed to connect to bitcoind. Error: {}", e_msg); - return; + std::process::exit(1); } }; From 75db9c65191005d2250cadf98e0a03ce367034f5 Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Mon, 6 Feb 2023 18:18:40 +0200 Subject: [PATCH 079/119] fixup: record the correct port for the Tor interface --- teos/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/src/main.rs b/teos/src/main.rs index 023eb28..8f079b9 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -293,7 +293,7 @@ async fn main() { .await; addresses.push(msgs::NetworkAddress::from_torv3( tor_api.get_onion_address(), - conf.api_port, + conf.onion_hidden_service_port, )); Some(tor_api) From 4bcfbf7c5d71efde446d5bafd08c41d35cf56ea9 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 26 Jan 2023 15:07:46 -0500 Subject: [PATCH 080/119] Formats strings to use inline params when possible --- teos-common/src/appointment.rs | 4 +-- teos-common/src/lib.rs | 9 ++--- teos-common/src/net/http.rs | 2 +- teos-common/src/net/mod.rs | 4 +-- teos/src/api/http.rs | 9 +++-- teos/src/api/internal.rs | 6 ++-- teos/src/api/tor.rs | 6 ++-- teos/src/bitcoin_cli.rs | 7 ++-- teos/src/carrier.rs | 23 ++++-------- teos/src/chain_monitor.rs | 2 +- teos/src/cli.rs | 10 +++--- teos/src/config.rs | 2 +- teos/src/dbm.rs | 44 ++++++++++------------- teos/src/main.rs | 19 +++++----- teos/src/responder.rs | 17 +++++---- teos/src/tls.rs | 15 +++----- teos/src/tx_index.rs | 4 +-- teos/src/watcher.rs | 39 +++++++++----------- watchtower-plugin/src/convert.rs | 53 ++++++++++++--------------- watchtower-plugin/src/dbm.rs | 7 ++-- watchtower-plugin/src/main.rs | 57 ++++++++++-------------------- watchtower-plugin/src/net/http.rs | 17 +++++---- watchtower-plugin/src/retrier.rs | 28 ++++++--------- watchtower-plugin/src/wt_client.rs | 39 ++++++-------------- 24 files changed, 165 insertions(+), 258 deletions(-) diff --git a/teos-common/src/appointment.rs b/teos-common/src/appointment.rs index 983b6a1..7ac1e0f 100644 --- a/teos-common/src/appointment.rs +++ b/teos-common/src/appointment.rs @@ -95,7 +95,7 @@ impl std::str::FromStr for AppointmentStatus { "being_watched" => Ok(AppointmentStatus::BeingWatched), "dispute_responded" => Ok(AppointmentStatus::DisputeResponded), "not_found" => Ok(AppointmentStatus::NotFound), - _ => Err(format!("Unknown status: {}", s)), + _ => Err(format!("Unknown status: {s}")), } } } @@ -107,7 +107,7 @@ impl fmt::Display for AppointmentStatus { AppointmentStatus::DisputeResponded => "dispute_responded", AppointmentStatus::NotFound => "not_found", }; - write!(f, "{}", s) + write!(f, "{s}") } } diff --git a/teos-common/src/lib.rs b/teos-common/src/lib.rs index 7c8b621..12a7df0 100644 --- a/teos-common/src/lib.rs +++ b/teos-common/src/lib.rs @@ -75,8 +75,7 @@ impl TryFrom for UserId { UserId::try_from(a.pop().unwrap()) } else { Err(format!( - "Unexpected json format. Expected a single parameter. Received: {}", - param_count + "Unexpected json format. Expected a single parameter. Received: {param_count}" )) } } @@ -84,8 +83,7 @@ impl TryFrom for UserId { let param_count = m.len(); if param_count > 1 { Err(format!( - "Unexpected json format. Expected a single parameter. Received: {}", - param_count + "Unexpected json format. Expected a single parameter. Received: {param_count}" )) } else { UserId::try_from(json!(m @@ -95,8 +93,7 @@ impl TryFrom for UserId { } } _ => Err(format!( - "Unexpected request format. Expected: user_id/tower_id. Received: '{}'", - value + "Unexpected request format. Expected: user_id/tower_id. Received: '{value}'" )), } } diff --git a/teos-common/src/net/http.rs b/teos-common/src/net/http.rs index 18450f9..86aa3df 100644 --- a/teos-common/src/net/http.rs +++ b/teos-common/src/net/http.rs @@ -22,6 +22,6 @@ impl std::fmt::Display for Endpoint { impl Endpoint { pub fn path(&self) -> String { - format!("/{}", self) + format!("/{self}") } } diff --git a/teos-common/src/net/mod.rs b/teos-common/src/net/mod.rs index 266e0c6..f9fd9fd 100644 --- a/teos-common/src/net/mod.rs +++ b/teos-common/src/net/mod.rs @@ -27,7 +27,7 @@ impl std::str::FromStr for AddressType { match s { "ipv4" => Ok(AddressType::IpV4), "torv3" => Ok(AddressType::TorV3), - _ => Err(format!("Unknown type: {}", s)), + _ => Err(format!("Unknown type: {s}")), } } } @@ -38,7 +38,7 @@ impl fmt::Display for AddressType { AddressType::IpV4 => "ipv4", AddressType::TorV3 => "torv3", }; - write!(f, "{}", s) + write!(f, "{s}") } } diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index f35b9b2..385c5d7 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -36,14 +36,14 @@ impl ApiError { fn missing_field(field_name: &str) -> Rejection { reject::custom(Self::new( - format!("missing field `{}`", field_name), + format!("missing field `{field_name}`"), errors::MISSING_FIELD, )) } fn empty_field(field_name: &str) -> Rejection { reject::custom(Self::new( - format!("`{}` field is empty", field_name), + format!("`{field_name}` field is empty"), errors::EMPTY_FIELD, )) } @@ -51,8 +51,7 @@ impl ApiError { fn wrong_field_length(field_name: &str, field_size: usize, expected_size: usize) -> Rejection { reject::custom(Self::new( format!( - "Wrong `{}` field size. Expected {}, received {}", - field_name, expected_size, field_size + "Wrong `{field_name}` field size. Expected {expected_size}, received {field_size}" ), errors::WRONG_FIELD_SIZE, )) @@ -104,7 +103,7 @@ fn parse_grpc_response( } Err(s) => { let (status_code, error_code) = match_status(&s); - log::debug!("Request failed, error_code={}", error_code); + log::debug!("Request failed, error_code={error_code}"); log::debug!("Response: {}", serde_json::json!(s.message())); ( reply::json(&ApiError::new(s.message().into(), error_code)), diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index 2fda02f..c6fa355 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -133,7 +133,7 @@ impl PublicTowerServices for Arc { )), AddAppointmentFailure::SubscriptionExpired(x) => Err(Status::new( Code::Unauthenticated, - format!("Your subscription expired at {}", x), + format!("Your subscription expired at {x}"), )), AddAppointmentFailure::AlreadyTriggered => Err(Status::new( Code::AlreadyExists, @@ -191,7 +191,7 @@ impl PublicTowerServices for Arc { )), GetAppointmentFailure::SubscriptionExpired(x) => Err(Status::new( Code::Unauthenticated, - format!("Your subscription expired at {}", x), + format!("Your subscription expired at {x}"), )), }, } @@ -213,7 +213,7 @@ impl PublicTowerServices for Arc { ), GetSubscriptionInfoFailure::SubscriptionExpired(x) => Status::new( Code::Unauthenticated, - format!("Your subscription expired at {}", x), + format!("Your subscription expired at {x}"), ), })?; diff --git a/teos/src/api/tor.rs b/teos/src/api/tor.rs index 0664272..d066125 100644 --- a/teos/src/api/tor.rs +++ b/teos/src/api/tor.rs @@ -49,7 +49,7 @@ impl TorAPI { log::info!("Loading Tor secret key from disk"); let key = fs::read(path.join("onion_v3_sk")) .await - .map_err(|e| log::warn!("Tor secret key cannot be loaded. {}", e)) + .map_err(|e| log::warn!("Tor secret key cannot be loaded. {e}")) .ok()?; let key: [u8; 64] = key .try_into() @@ -62,7 +62,7 @@ impl TorAPI { /// Stores a Tor key to disk. async fn store_sk(key: &TorSecretKeyV3, path: PathBuf) { if let Err(e) = fs::write(path.join("onion_v3_sk"), key.as_bytes()).await { - log::error!("Cannot store Tor secret key. {}", e); + log::error!("Cannot store Tor secret key. {e}"); } } @@ -125,7 +125,7 @@ impl TorAPI { .map_err(|e| { Error::new( ErrorKind::Other, - format!("failed to create onion hidden service: {}", e), + format!("failed to create onion hidden service: {e}"), ) })?; diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index ee6bb99..80b805c 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -79,7 +79,7 @@ impl<'a> BitcoindClient<'a> { teos_network: &'a str, ) -> std::io::Result> { let http_endpoint = HttpEndpoint::for_host(host.to_owned()).with_port(port); - let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password)); + let rpc_credentials = base64::encode(&format!("{rpc_user}:{rpc_password}")); let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; let client = Self { @@ -97,10 +97,7 @@ impl<'a> BitcoindClient<'a> { if btc_network != teos_network { Err(Error::new( ErrorKind::InvalidInput, - format!( - "bitcoind is running on {} but teosd is set to run on {}", - btc_network, teos_network - ), + format!("bitcoind is running on {btc_network} but teosd is set to run on {teos_network}"), )) } else { Ok(client) diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 6b576e1..15c9d83 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -96,11 +96,11 @@ impl Carrier { Err(JsonRpcError(RpcError(rpcerr))) => match rpcerr.code { // Since we're pushing a raw transaction to the network we can face several rejections rpc_errors::RPC_VERIFY_REJECTED => { - log::error!("Transaction couldn't be broadcast. {:?}", rpcerr); + log::error!("Transaction couldn't be broadcast. {rpcerr:?}"); ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_REJECTED) } rpc_errors::RPC_VERIFY_ERROR => { - log::error!("Transaction couldn't be broadcast. {:?}", rpcerr); + log::error!("Transaction couldn't be broadcast. {rpcerr:?}"); ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_ERROR) } rpc_errors::RPC_VERIFY_ALREADY_IN_CHAIN => { @@ -122,10 +122,7 @@ impl Carrier { } _ => { // If something else happens (unlikely but possible) log it so we can treat it in future releases. - log::error!( - "Unexpected rpc error when calling sendrawtransaction: {:?}", - rpcerr - ); + log::error!("Unexpected rpc error when calling sendrawtransaction: {rpcerr:?}"); ConfirmationStatus::Rejected(errors::UNKNOWN_JSON_RPC_EXCEPTION) } }, @@ -137,7 +134,7 @@ impl Carrier { } Err(e) => { // TODO: This may need finer catching. - log::error!("Unexpected error when calling sendrawtransaction: {:?}", e); + log::error!("Unexpected error when calling sendrawtransaction: {e:?}"); ConfirmationStatus::Rejected(errors::UNKNOWN_JSON_RPC_EXCEPTION) } }; @@ -159,15 +156,12 @@ impl Carrier { Ok(tx) => tx.blockhash.is_none(), Err(JsonRpcError(RpcError(rpcerr))) => match rpcerr.code { rpc_errors::RPC_INVALID_ADDRESS_OR_KEY => { - log::info!("Transaction not found in mempool: {}", txid); + log::info!("Transaction not found in mempool: {txid}"); false } e => { // DISCUSS: This could result in a silent error with unknown consequences - log::error!( - "Unexpected error code when calling getrawtransaction: {}", - e - ); + log::error!("Unexpected error code when calling getrawtransaction: {e}"); false } }, @@ -180,10 +174,7 @@ impl Carrier { // TODO: This may need finer catching. Err(e) => { // DISCUSS: This could result in a silent error with unknown consequences - log::error!( - "Unexpected JSONRPCError when calling getrawtransaction: {}", - e - ); + log::error!("Unexpected JSONRPCError when calling getrawtransaction: {e}"); false } } diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 4314a51..0119267 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -100,7 +100,7 @@ where Err(e) => match e.kind() { BlockSourceErrorKind::Persistent => { // FIXME: This may need finer catching - log::error!("Unexpected persistent error: {:?}", e); + log::error!("Unexpected persistent error: {e:?}"); } BlockSourceErrorKind::Transient => { // Treating all transient as connection errors at least for now. diff --git a/teos/src/cli.rs b/teos/src/cli.rs index c181894..5c20f00 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -20,7 +20,7 @@ async fn main() { // Create data dir if it does not exist fs::create_dir_all(&path).await.unwrap_or_else(|e| { - eprintln!("Cannot create data dir: {:?}", e); + eprintln!("Cannot create data dir: {e:?}"); std::process::exit(1); }); @@ -51,13 +51,13 @@ async fn main() { .expect("Cannot create channel from endpoint") .tls_config(tls) .unwrap_or_else(|e| { - eprintln!("Could not configure tls: {:?}", e); + eprintln!("Could not configure tls: {e:?}"); std::process::exit(1); }) .connect() .await .unwrap_or_else(|e| { - eprintln!("Could not connect to tower: {:?}", e); + eprintln!("Could not connect to tower: {e:?}"); std::process::exit(1); }); @@ -83,7 +83,7 @@ async fn main() { Err(status) => println!("{}", status.message()), } } - Err(e) => println!("{}", e), + Err(e) => println!("{e}"), }; } Command::GetTowerInfo => { @@ -109,7 +109,7 @@ async fn main() { Err(status) => println!("{}", status.message()), } } - Err(e) => println!("{}", e), + Err(e) => println!("{e}"), }; } Command::Stop => { diff --git a/teos/src/config.rs b/teos/src/config.rs index 54e7a99..1669b25 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -20,7 +20,7 @@ pub fn from_file(path: PathBuf) -> T { match std::fs::read(path) { Ok(file_content) => toml::from_slice::(&file_content).map_or_else( |e| { - eprintln!("Couldn't parse config file: {}", e); + eprintln!("Couldn't parse config file: {e}"); T::default() }, |config| config, diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index db1636f..af27214 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -107,11 +107,11 @@ impl DBM { ], ) { Ok(x) => { - log::debug!("User successfully stored: {}", user_id); + log::debug!("User successfully stored: {user_id}"); Ok(x) } Err(e) => { - log::error!("Couldn't store user: {}. Error: {:?}", user_id, e); + log::error!("Couldn't store user: {user_id}. Error: {e:?}"); Err(e) } } @@ -131,10 +131,10 @@ impl DBM { ], ) { Ok(_) => { - log::debug!("User's info successfully updated: {}", user_id); + log::debug!("User's info successfully updated: {user_id}"); } Err(_) => { - log::error!("User not found, data cannot be updated: {}", user_id); + log::error!("User not found, data cannot be updated: {user_id}"); } } } @@ -205,18 +205,15 @@ impl DBM { let query = "DELETE FROM users WHERE user_id IN ".to_owned(); let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); - match tx.execute( - &format!("{}{}", query, placeholders), - params_from_iter(chunk), - ) { + match tx.execute(&format!("{query}{placeholders}"), params_from_iter(chunk)) { Ok(_) => log::debug!("Users deletion added to db transaction"), - Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {:?}", e), + Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {e:?}"), } } match tx.commit() { Ok(_) => log::debug!("Users successfully deleted"), - Err(e) => log::error!("Couldn't delete users. Error: {:?}", e), + Err(e) => log::error!("Couldn't delete users. Error: {e:?}"), } (users.len() as f64 / limit as f64).ceil() as usize @@ -242,11 +239,11 @@ impl DBM { ], ) { Ok(x) => { - log::debug!("Appointment successfully stored: {}", uuid); + log::debug!("Appointment successfully stored: {uuid}"); Ok(x) } Err(e) => { - log::error!("Couldn't store appointment: {}. Error: {:?}", uuid, e); + log::error!("Couldn't store appointment: {uuid}. Error: {e:?}"); Err(e) } } @@ -268,10 +265,10 @@ impl DBM { ], ) { Ok(_) => { - log::debug!("Appointment successfully updated: {}", uuid); + log::debug!("Appointment successfully updated: {uuid}"); } Err(_) => { - log::error!("Appointment not found, data cannot be updated: {}", uuid); + log::error!("Appointment not found, data cannot be updated: {uuid}"); } } } @@ -360,10 +357,10 @@ impl DBM { let query = "DELETE FROM appointments WHERE UUID=(?)"; match self.remove_data(query, params![uuid.to_vec()]) { Ok(_) => { - log::debug!("Appointment successfully removed: {}", uuid); + log::debug!("Appointment successfully removed: {uuid}"); } Err(_) => { - log::error!("Appointment not found, data cannot be removed: {}", uuid); + log::error!("Appointment not found, data cannot be removed: {uuid}"); } } } @@ -386,12 +383,9 @@ impl DBM { let query = "DELETE FROM appointments WHERE UUID IN ".to_owned(); let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); - match tx.execute( - &format!("{}{}", query, placeholders), - params_from_iter(chunk), - ) { + match tx.execute(&format!("{query}{placeholders}"), params_from_iter(chunk)) { Ok(_) => log::debug!("Appointments deletion added to db transaction"), - Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {:?}", e), + Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {e:?}"), } } @@ -399,13 +393,13 @@ impl DBM { let query = "UPDATE users SET available_slots=(?1) WHERE user_id=(?2)"; match tx.execute(query, params![info.available_slots, id.to_vec(),]) { Ok(_) => log::debug!("User update added to db transaction"), - Err(e) => log::error!("Couldn't add update query to transaction. Error: {:?}", e), + Err(e) => log::error!("Couldn't add update query to transaction. Error: {e:?}"), }; } match tx.commit() { Ok(_) => log::debug!("Appointments successfully deleted"), - Err(e) => log::error!("Couldn't delete appointments. Error: {:?}", e), + Err(e) => log::error!("Couldn't delete appointments. Error: {e:?}"), } (appointments.len() as f64 / limit as f64).ceil() as usize @@ -446,11 +440,11 @@ impl DBM { ], ) { Ok(x) => { - log::debug!("Tracker successfully stored: {}", uuid); + log::debug!("Tracker successfully stored: {uuid}"); Ok(x) } Err(e) => { - log::error!("Couldn't store tracker: {}. Error: {:?}", uuid, e); + log::error!("Couldn't store tracker: {uuid}. Error: {e:?}"); Err(e) } } diff --git a/teos/src/main.rs b/teos/src/main.rs index 8f079b9..97a882c 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -72,7 +72,7 @@ async fn main() { // Create data dir if it does not exist fs::create_dir_all(&path).unwrap_or_else(|e| { - eprintln!("Cannot create data dir: {:?}", e); + eprintln!("Cannot create data dir: {e:?}"); std::process::exit(1); }); @@ -81,7 +81,7 @@ async fn main() { let is_default = conf.is_default(); conf.patch_with_options(opt); conf.verify().unwrap_or_else(|e| { - eprintln!("{}", e); + eprintln!("{e}"); std::process::exit(1); }); @@ -112,7 +112,7 @@ async fn main() { // Create network dir let path_network = path.join(conf.btc_network.clone()); fs::create_dir_all(&path_network).unwrap_or_else(|e| { - eprintln!("Cannot create network dir: {:?}", e); + eprintln!("Cannot create network dir: {e:?}"); std::process::exit(1); }); let dbm = Arc::new(Mutex::new( @@ -136,7 +136,7 @@ async fn main() { } } }; - log::info!("tower_id: {}", tower_pk); + log::info!("tower_id: {tower_pk}"); // Initialize our bitcoind client let (bitcoin_cli, bitcoind_reachable) = match BitcoindClient::new( @@ -157,7 +157,7 @@ async fn main() { ErrorKind::InvalidData => "invalid btcrpcuser or btcrpcpassword".into(), _ => e.to_string(), }; - log::error!("Failed to connect to bitcoind. Error: {}", e_msg); + log::error!("Failed to connect to bitcoind. Error: {e_msg}"); std::process::exit(1); } }; @@ -171,7 +171,7 @@ async fn main() { }; let rpc = Arc::new( Client::new( - &format!("{}{}:{}", schema, conf.btc_rpc_connect, conf.btc_rpc_port), + &format!("{schema}{}:{}", conf.btc_rpc_connect, conf.btc_rpc_port), Auth::UserPass(conf.btc_rpc_user.clone(), conf.btc_rpc_password.clone()), ) .unwrap(), @@ -196,8 +196,7 @@ async fn main() { // could pull from the backend. Adding this functionality just for regtest seemed unnecessary though, hence the check. if tip.height < IRREVOCABLY_RESOLVED { log::error!( - "Not enough blocks to start teosd (required: {}). Mine at least {} more", - IRREVOCABLY_RESOLVED, + "Not enough blocks to start teosd (required: {IRREVOCABLY_RESOLVED}). Mine at least {} more", IRREVOCABLY_RESOLVED - tip.height ); std::process::exit(1); @@ -323,7 +322,7 @@ async fn main() { // Generate mtls certificates to data directory so the admin can securely connect // to the server to perform administrative tasks. let (identity, ca_cert) = tls_init(&path).unwrap_or_else(|e| { - eprintln!("Couldn't generate tls certificates: {:?}", e); + eprintln!("Couldn't generate tls certificates: {e:?}"); std::process::exit(1); }); @@ -370,7 +369,7 @@ async fn main() { .expose_onion_service(tor_service_ready, shutdown_signal_tor) .await { - eprintln!("Cannot connect to the Tor backend: {}", e); + eprintln!("Cannot connect to the Tor backend: {e}"); std::process::exit(1); } })); diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 6616949..0a0a1cd 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -263,7 +263,7 @@ impl Responder { .unwrap() .store_tracker(uuid, &tracker) .unwrap(); - log::info!("New tracker added (uuid={}).", uuid); + log::info!("New tracker added (uuid={uuid})"); } /// Checks whether a given tracker can be found in the [Responder]. @@ -318,7 +318,7 @@ impl Responder { // Tracker is deep enough in the chain, it can be deleted completed_trackers.insert(*uuid); } else { - log::info!("{} received a confirmation (count={})", uuid, confirmations); + log::info!("{uuid} received a confirmation (count={confirmations})"); } } else if txids.contains(&tracker.penalty_txid) { // First confirmation was received @@ -423,9 +423,8 @@ impl Responder { let status = carrier.send_transaction(&dispute_tx); if let ConfirmationStatus::Rejected(e) = status { log::error!( - "Reorged dispute transaction rejected during rebroadcast: {} (reason: {:?})", - dispute_tx.txid(), - e + "Reorged dispute transaction rejected during rebroadcast: {} (reason: {e})", + dispute_tx.txid() ); status } else { @@ -466,9 +465,9 @@ impl Responder { let mut tx_tracker_map = self.tx_tracker_map.lock().unwrap(); for uuid in uuids.iter() { match reason { - DeletionReason::Completed => log::info!("Appointment completed. Penalty transaction was irrevocably confirmed: {}", uuid), - DeletionReason::Outdated => log::info!("Appointment couldn't be completed. Expiry reached but penalty didn't make it to the chain: {}", uuid), - DeletionReason::Rejected => log::info!("Appointment couldn't be completed. Either the dispute or the penalty txs where rejected during rebroadcast: {}", uuid), + DeletionReason::Completed => log::info!("Appointment completed. Penalty transaction was irrevocably confirmed: {uuid}"), + DeletionReason::Outdated => log::info!("Appointment couldn't be completed. Expiry reached but penalty didn't make it to the chain: {uuid}"), + DeletionReason::Rejected => log::info!("Appointment couldn't be completed. Either the dispute or the penalty txs where rejected during rebroadcast: {uuid}"), } match trackers.remove(uuid) { @@ -488,7 +487,7 @@ impl Responder { } None => { // This should never happen. Logging just in case so we can fix it if so - log::error!("Completed tracker not found when cleaning: {}", uuid); + log::error!("Completed tracker not found when cleaning: {uuid}"); } } } diff --git a/teos/src/tls.rs b/teos/src/tls.rs index d126a40..d31783e 100644 --- a/teos/src/tls.rs +++ b/teos/src/tls.rs @@ -67,21 +67,14 @@ fn generate_or_load_identity( parent: Option<&Identity>, ) -> Result { // Just our naming convention here. - let cert_path = directory.join(format!("{}.pem", filename)); - let key_path = directory.join(format!("{}-key.pem", filename)); + let cert_path = directory.join(format!("{filename}.pem")); + let key_path = directory.join(format!("{filename}-key.pem")); // Did we have to generate a new key? In that case we also need to regenerate the certificate. if !key_path.exists() || !cert_path.exists() { - log::debug!( - "Generating a new keypair in {:?}, it didn't exist", - &key_path - ); + log::debug!("Generating a new keypair in {key_path:?}, it didn't exist",); let keypair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?; std::fs::write(&key_path, keypair.serialize_pem())?; - log::debug!( - "Generating a new certificate for key {:?} at {:?}", - &key_path, - &cert_path - ); + log::debug!("Generating a new certificate for key {key_path:?} at {cert_path:?}",); // Configure the certificate we want. let subject_alt_names = vec!["cln".to_string(), "localhost".to_string()]; diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 1ff7052..fd7c2b9 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -189,7 +189,7 @@ where // Blocks should be disconnected from last backwards. Log if that's not the case so we can revisit this and fix it. if let Some(ref h) = self.blocks.pop_back() { if h != block_hash { - log::error!("Disconnected block does not match the oldest block stored in the TxIndex ({} != {})", block_hash, h); + log::error!("Disconnected block does not match the oldest block stored in the TxIndex ({block_hash} != {h})"); } } } else { @@ -204,7 +204,7 @@ where let ks = self.tx_in_block.remove(&h).unwrap(); self.index.retain(|k, _| !ks.contains(k)); - log::info!("Oldest block removed from index: {}", h); + log::info!("Oldest block removed from index: {h}"); } } diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index a9672a1..b94d6b2 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -217,7 +217,7 @@ impl Watcher { let uuid = UUID::new(extended_appointment.locator(), user_id); if self.responder.has_tracker(uuid) { - log::info!("Tracker for {} already found in Responder", uuid); + log::info!("Tracker for {uuid} already found in Responder"); return Err(AddAppointmentFailure::AlreadyTriggered); } @@ -286,9 +286,8 @@ impl Watcher { .insert(uuid) { log::debug!( - "Adding an additional appointment to locator {}: {}", - appointment.locator(), - uuid + "Adding an additional appointment to locator {}: {uuid}", + appointment.locator() ); self.dbm .lock() @@ -297,7 +296,7 @@ impl Watcher { .unwrap(); StoredAppointment::Collision } else { - log::debug!("Update received for {}, locator map not modified", uuid); + log::debug!("Update received for {uuid}, locator map not modified"); self.dbm .lock() .unwrap() @@ -339,7 +338,7 @@ impl Watcher { ) { // DISCUSS: We could either free the slots or keep it occupied as if this was misbehavior. // Keeping it for now. - log::warn!("Appointment bounced in the Responder. Reason: {:?}", reason); + log::warn!("Appointment bounced in the Responder. Reason: {reason:?}"); self.dbm.lock().unwrap().remove_appointment(uuid); TriggeredAppointment::Rejected @@ -375,7 +374,7 @@ impl Watcher { locator: Locator, user_signature: &str, ) -> Result { - let message = format!("get appointment {}", locator); + let message = format!("get appointment {locator}"); let user_id = self .gatekeeper @@ -405,7 +404,7 @@ impl Watcher { .get_tracker(uuid) .map(AppointmentInfo::Tracker) .ok_or_else(|| { - log::info!("Cannot find {}", locator); + log::info!("Cannot find {locator}"); GetAppointmentFailure::NotFound }) } @@ -511,16 +510,14 @@ impl Watcher { for uuid in uuids { match reason { - DeletionReason::Outdated => log::info!( - "End time reached by {} without breach. Deleting appointment", - uuid - ), + DeletionReason::Outdated => { + log::info!("End time reached by {uuid} without breach. Deleting appointment") + } DeletionReason::Invalid => log::info!( - "{} cannot be completed, it contains invalid data. Deleting appointment", - uuid + "{uuid} cannot be completed, it contains invalid data. Deleting appointment" ), DeletionReason::Accepted => { - log::info!("{} accepted by the Responder. Deleting appointment", uuid) + log::info!("{uuid} accepted by the Responder. Deleting appointment") } }; match appointments.remove(uuid) { @@ -537,7 +534,7 @@ impl Watcher { } None => { // This should never happen. Logging just in case so we can fix it if so - log::error!("Appointment not found when cleaning: {}", uuid); + log::error!("Appointment not found when cleaning: {uuid}"); } } } @@ -642,12 +639,11 @@ impl Watcher { match dbm.load_locator(*uuid) { Ok(locator) => locators.push(locator), Err(_) => log::error!( - "Tracker found in Responder but not in DB (uuid = {})", - uuid + "Tracker found in Responder but not in DB (uuid = {uuid})" ), } } else { - log::error!("Appointment found in the Gatekeeper but not in the Watcher nor the Responder (uuid = {})", uuid) + log::error!("Appointment found in the Gatekeeper but not in the Watcher nor the Responder (uuid = {uuid})") } } } @@ -703,10 +699,7 @@ impl chain::Listen for Watcher { let mut appointments_to_delete = HashSet::from_iter(invalid_breaches.into_keys()); let mut delivered_appointments = HashSet::new(); for (uuid, breach) in valid_breaches { - log::info!( - "Notifying Responder and deleting appointment (uuid: {})", - uuid - ); + log::info!("Notifying Responder and deleting appointment (uuid: {uuid})"); if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( uuid, diff --git a/watchtower-plugin/src/convert.rs b/watchtower-plugin/src/convert.rs index db86447..ec1b9d0 100644 --- a/watchtower-plugin/src/convert.rs +++ b/watchtower-plugin/src/convert.rs @@ -22,10 +22,10 @@ pub enum RegisterError { impl std::fmt::Display for RegisterError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - RegisterError::InvalidId(x) => write!(f, "{}", x), - RegisterError::InvalidHost(x) => write!(f, "{}", x), - RegisterError::InvalidPort(x) => write!(f, "{}", x), - RegisterError::InvalidFormat(x) => write!(f, "{}", x), + RegisterError::InvalidId(x) => write!(f, "{x}"), + RegisterError::InvalidHost(x) => write!(f, "{x}"), + RegisterError::InvalidPort(x) => write!(f, "{x}"), + RegisterError::InvalidFormat(x) => write!(f, "{x}"), } } } @@ -80,8 +80,7 @@ impl RegisterParams { fn with_port(self, port: u64) -> Result { if port > u16::MAX as u64 { Err(RegisterError::InvalidPort(format!( - "port must be a 16-byte integer. Received: {}", - port + "port must be a 16-byte integer. Received: {port}" ))) } else { Ok(Self { @@ -109,7 +108,7 @@ impl TryFrom for RegisterParams { let port = if let Some(p) = v.next() { p.parse() .map(Some) - .map_err(|_| RegisterError::InvalidPort(format!("Port is not a number: {}", p)))? + .map_err(|_| RegisterError::InvalidPort(format!("Port is not a number: {p}")))? } else { None }; @@ -128,14 +127,14 @@ impl TryFrom for RegisterParams { let tower_id = a.get(0).unwrap().as_str().ok_or_else(|| RegisterError::InvalidId("tower_id must be a string".to_string()))?; let host = Some(a.get(1).unwrap().as_str().ok_or_else(|| RegisterError::InvalidHost("host must be a string".to_string()))?); let port = if let Some(p) = a.get(2) { - Some(p.as_u64().ok_or_else(|| RegisterError::InvalidPort(format!("port must be a number. Received: {}", p)))?) + Some(p.as_u64().ok_or_else(|| RegisterError::InvalidPort(format!("port must be a number. Received: {p}")))?) } else { None }; RegisterParams::new(tower_id, host, port) } - _ => Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {}", param_count))), + _ => Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {param_count}"))), } }, serde_json::Value::Object(mut m) => { @@ -143,7 +142,7 @@ impl TryFrom for RegisterParams { let param_count = m.len(); if m.is_empty() || param_count > allowed_keys.len() { - Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {}", param_count))) + Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {param_count}"))) } else if !m.contains_key(allowed_keys[0]){ Err(RegisterError::InvalidId(format!("{} is mandatory", allowed_keys[0]))) } else if !m.iter().all(|(k, _)| allowed_keys.contains(&k.as_str())) { @@ -160,7 +159,7 @@ impl TryFrom for RegisterParams { } }, _ => Err(RegisterError::InvalidFormat( - format!("Unexpected request format. Expected: 'tower_id[@host][:port]' or 'tower_id [host] [port]'. Received: '{}'", value), + format!("Unexpected request format. Expected: 'tower_id[@host][:port]' or 'tower_id [host] [port]'. Received: '{value}'"), )), } } @@ -177,9 +176,9 @@ pub enum GetAppointmentError { impl std::fmt::Display for GetAppointmentError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - GetAppointmentError::InvalidId(x) => write!(f, "{}", x), - GetAppointmentError::InvalidLocator(x) => write!(f, "{}", x), - GetAppointmentError::InvalidFormat(x) => write!(f, "{}", x), + GetAppointmentError::InvalidId(x) => write!(f, "{x}"), + GetAppointmentError::InvalidLocator(x) => write!(f, "{x}"), + GetAppointmentError::InvalidFormat(x) => write!(f, "{x}"), } } } @@ -200,8 +199,7 @@ impl TryFrom for GetAppointmentParams { let param_count = a.len(); if param_count != 2 { Err(GetAppointmentError::InvalidFormat(format!( - "Unexpected request format. The request needs 2 parameter. Received: {}", - param_count + "Unexpected request format. The request needs 2 parameter. Received: {param_count}" ))) } else { let tower_id = if let Some(s) = a.get(0).unwrap().as_str() { @@ -240,8 +238,7 @@ impl TryFrom for GetAppointmentParams { for k in allowed_keys.iter() { if !m.contains_key(*k) { return Err(GetAppointmentError::InvalidFormat(format!( - "{} is mandatory", - k + "{k} is mandatory" ))); } } @@ -255,8 +252,7 @@ impl TryFrom for GetAppointmentParams { GetAppointmentParams::try_from(json!(params)) } _ => Err(GetAppointmentError::InvalidFormat(format!( - "Unexpected request format. Expected: tower_id locator. Received: '{}'", - value + "Unexpected request format. Expected: tower_id locator. Received: '{value}'" ))), } } @@ -338,21 +334,18 @@ mod tests { #[test] fn test_try_from_json_string() { let ok = [ - format!("{}@host:80", VALID_ID), - format!("{}@host", VALID_ID), + format!("{VALID_ID}@host:80"), + format!("{VALID_ID}@host"), VALID_ID.to_string(), ]; let wrong_id = ["", "id@host:80", "@host:80", "@:80"]; let wrong_host = [ - format!("{}@", VALID_ID), - format!("{}@ ", VALID_ID), - format!("{}@ host", VALID_ID), - format!("{}@:80", VALID_ID), - ]; - let wrong_port = [ - format!("{}@host:", VALID_ID), - format!("{}@host:port", VALID_ID), + format!("{VALID_ID}@"), + format!("{VALID_ID}@ "), + format!("{VALID_ID}@ host"), + format!("{VALID_ID}@:80"), ]; + let wrong_port = [format!("{VALID_ID}@host:"), format!("{VALID_ID}@host:port")]; for s in ok { let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]); diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index 1c1a828..ec03886 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -393,10 +393,7 @@ impl DBM { // TODO: Can this be prepared instead of formatted (using ?1 seems to fail)? let mut stmt = self .connection - .prepare(&format!( - "SELECT locator FROM {} WHERE tower_id = ?", - status - )) + .prepare(&format!("SELECT locator FROM {status} WHERE tower_id = ?")) .unwrap(); let mut rows = stmt.query(params![tower_id.to_vec()]).unwrap(); @@ -552,7 +549,7 @@ impl DBM { let mut appointments = Vec::new(); let mut stmt = self .connection - .prepare(&format!("SELECT a.locator, a.encrypted_blob, a.to_self_delay FROM appointments as a, {} as t WHERE a.locator = t.locator AND t.tower_id = ?", table)) + .prepare(&format!("SELECT a.locator, a.encrypted_blob, a.to_self_delay FROM appointments as a, {table} as t WHERE a.locator = t.locator AND t.tower_id = ?")) .unwrap(); let mut rows = stmt.query([tower_id.to_vec()]).unwrap(); diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 1e48a07..f7c0fbf 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -33,7 +33,7 @@ fn to_cln_error(e: RequestError) -> Error { RequestError::DeserializeError(e) => anyhow!(e), RequestError::Unexpected(e) => anyhow!(e), }; - log::info!("{}", e); + log::info!("{e}"); e } @@ -50,11 +50,7 @@ fn send_to_retrier(state: &MutexGuard, tower_id: TowerId, locator: Loc .send((tower_id, RevocationData::Fresh(locator))) .unwrap(); } else { - log::debug!( - "Not sending data to idle retrier ({}, {})", - tower_id, - locator - ) + log::debug!("Not sending data to idle retrier ({tower_id}, {locator})") } } @@ -85,9 +81,9 @@ async fn register( let tower_net_addr = { if !host.starts_with("http://") { - host = format!("http://{}", host) + host = format!("http://{host}") } - NetAddr::new(format!("{}:{}", host, port)) + NetAddr::new(format!("{host}:{port}")) }; let proxy = plugin.state().lock().unwrap().proxy.clone(); @@ -141,10 +137,7 @@ async fn get_registration_receipt( let state = plugin.state().lock().unwrap(); let response = state.get_registration_receipt(tower_id).map_err(|_| { - anyhow!( - "Cannot find {} within the known towers. Have you registered?", - tower_id - ) + anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") })?; Ok(json!(response)) @@ -162,7 +155,7 @@ async fn get_subscription_info( if let Some(info) = state.towers.get(&tower_id) { Ok((state.user_sk, info.net_addr.clone(), state.proxy.clone())) } else { - Err(anyhow!("Unknown tower id: {}", tower_id)) + Err(anyhow!("Unknown tower id: {tower_id}")) } }?; @@ -291,10 +284,7 @@ async fn get_tower_info( let state = plugin.state().lock().unwrap(); let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; let tower_info = state.load_tower_info(tower_id).map_err(|_| { - anyhow!( - "Cannot find {} within the known towers. Have you registered?", - tower_id - ) + anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") })?; // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable @@ -323,7 +313,7 @@ async fn retry_tower( .map_err(|e| anyhow!(e))?; } else { // Status can only be running or idle for data in the retriers map. - return Err(anyhow!("{} is already being retried", tower_id)); + return Err(anyhow!("{tower_id} is already being retried")); } } else if tower_status.is_retryable() { // We do send associated data here given there is no retrier associated to this tower. @@ -349,9 +339,9 @@ async fn retry_tower( )); } } else { - return Err(anyhow!("Unknown tower {}", tower_id)); + return Err(anyhow!("Unknown tower {tower_id}")); } - Ok(json!(format!("Retrying {}", tower_id))) + Ok(json!(format!("Retrying {tower_id}"))) } /// Forgets about a tower wiping out all local data associated to it. @@ -363,9 +353,9 @@ async fn abandon_tower( let mut state = plugin.state().lock().unwrap(); if state.towers.get(&tower_id).is_some() { state.remove_tower(tower_id).unwrap(); - Ok(json!(format!("{} successfully abandoned", tower_id))) + Ok(json!(format!("{tower_id} successfully abandoned"))) } else { - Err(anyhow!("Unknown tower {}", tower_id)) + Err(anyhow!("Unknown tower {tower_id}")) } } @@ -377,7 +367,7 @@ async fn on_commitment_revocation( v: serde_json::Value, ) -> Result { let commitment_revocation = serde_json::from_value::(v) - .map_err(|e| anyhow!("Cannot decode commitment_revocation data. Error: {}", e))?; + .map_err(|e| anyhow!("Cannot decode commitment_revocation data. Error: {e}"))?; log::debug!( "New commitment revocation received for channel {}. Commit number {}", commitment_revocation.channel_id, @@ -430,8 +420,7 @@ async fn on_commitment_revocation( AddAppointmentError::RequestError(e) => { if e.is_connection() { log::warn!( - "{} cannot be reached. Adding {} to pending appointments", - tower_id, + "{tower_id} cannot be reached. Adding {} to pending appointments", appointment.locator ); let mut state = plugin.state().lock().unwrap(); @@ -443,8 +432,7 @@ async fn on_commitment_revocation( AddAppointmentError::ApiError(e) => match e.error_code { errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { log::warn!( - "There is a subscription issue with {}. Adding {} to pending", - tower_id, + "There is a subscription issue with {tower_id}. Adding {} to pending", appointment.locator ); let mut state = plugin.state().lock().unwrap(); @@ -455,8 +443,7 @@ async fn on_commitment_revocation( _ => { log::warn!( - "{} rejected the appointment. Error: {}, error_code: {}", - tower_id, + "{tower_id} rejected the appointment. Error: {}, error_code: {}", e.error, e.error_code ); @@ -478,22 +465,16 @@ async fn on_commitment_revocation( }, }; } else if status.is_misbehaving() { - log::warn!( - "{} is misbehaving. Not sending any further appointments", - tower_id - ); + log::warn!("{tower_id} is misbehaving. Not sending any further appointments",); } else { if status.is_subscription_error() { log::warn!( - "There is a subscription issue with {}. Adding {} to pending", - tower_id, + "There is a subscription issue with {tower_id}. Adding {} to pending", appointment.locator ); } else { log::warn!( - "{} is {}. Adding {} to pending", - tower_id, - status, + "{tower_id} is {status}. Adding {} to pending", appointment.locator, ); } diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index fc063a0..23448a5 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -62,7 +62,7 @@ pub async fn register( tower_net_addr: &NetAddr, proxy: &Option, ) -> Result { - log::info!("Registering in the Eye of Satoshi (tower_id={})", tower_id); + log::info!("Registering in the Eye of Satoshi (tower_id={tower_id})"); process_post_response( post_request( tower_net_addr, @@ -95,13 +95,12 @@ pub async fn add_appointment( signature: &str, ) -> Result<(u32, AppointmentReceipt), AddAppointmentError> { log::debug!( - "Sending appointment {} to tower {}", - appointment.locator, - tower_id + "Sending appointment {} to tower {tower_id}", + appointment.locator ); let (response, receipt) = send_appointment(tower_id, tower_net_addr, proxy, appointment, signature).await?; - log::debug!("Appointment accepted and signed by {}", tower_id); + log::debug!("Appointment accepted and signed by {tower_id}"); log::debug!("Remaining slots: {}", response.available_slots); log::debug!("Start block: {}", response.start_block); @@ -167,10 +166,10 @@ pub async fn post_request( reqwest::Client::builder() .proxy( reqwest::Proxy::http(proxy.get_socks_addr()) - .map_err(|e| RequestError::ConnectionError(format!("{}", e)))?, + .map_err(|e| RequestError::ConnectionError(format!("{e}")))?, ) .build() - .map_err(|e| RequestError::ConnectionError(format!("{}", e)))? + .map_err(|e| RequestError::ConnectionError(format!("{e}")))? } else { reqwest::Client::new() } @@ -190,7 +189,7 @@ pub async fn post_request( .send() .await .map_err(|e| { - log::debug!("An error ocurred when sending data to the tower: {}", e); + log::debug!("An error ocurred when sending data to the tower: {e}"); if e.is_connect() | e.is_timeout() { RequestError::ConnectionError( "Cannot connect to the tower. Connection refused".to_owned(), @@ -210,7 +209,7 @@ pub async fn process_post_response( // TODO: Check if this can be switched for a map. Not sure how to handle async with maps match post_request { Ok(r) => r.json().await.map_err(|e| { - RequestError::DeserializeError(format!("Unexpected response body. Error: {}", e)) + RequestError::DeserializeError(format!("Unexpected response body. Error: {e}")) }), Err(e) => Err(e), } diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index a66416a..b980a40 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -30,7 +30,7 @@ enum RetryError { impl Display for RetryError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - RetryError::Subscription(r, _) => write!(f, "{}", r), + RetryError::Subscription(r, _) => write!(f, "{r}"), RetryError::Unreachable => write!(f, "Tower cannot be reached"), RetryError::Misbehaving(_) => write!(f, "Tower misbehaved"), RetryError::Abandoned => write!(f, "Tower was abandoned. Skipping retry"), @@ -99,11 +99,11 @@ impl RetryManager { .towers .contains_key(&tower_id) { - log::info!("Skipping retrying abandoned tower {}", tower_id); + log::info!("Skipping retrying abandoned tower {tower_id}"); } else if let Some(retrier) = self.retriers.get(&tower_id) { if retrier.is_idle() { if !data.is_none() { - log::error!("Data was send to an idle retier. This should have never happened. Please report! ({:?})", data); + log::error!("Data was send to an idle retier. This should have never happened. Please report! ({data:?})"); continue; } log::info!( @@ -181,7 +181,7 @@ impl RetryManager { /// If the tower is not currently being retried, a new entry for it is created, otherwise, the data is appended to the existing entry. fn add_pending_appointments(&mut self, tower_id: TowerId, locators: HashSet) { if let std::collections::hash_map::Entry::Vacant(e) = self.retriers.entry(tower_id) { - log::debug!("Creating a new entry for tower {} ", tower_id); + log::debug!("Creating a new entry for tower {tower_id}"); e.insert(Arc::new(Retrier::new( self.wt_client.clone(), tower_id, @@ -196,11 +196,7 @@ impl RetryManager { .lock() .unwrap(); for locator in locators { - log::debug!( - "Adding pending appointment {} to existing tower {}", - locator, - tower_id - ); + log::debug!("Adding pending appointment {locator} to existing tower {tower_id}",); pending_appointments.insert(locator); } } @@ -373,7 +369,7 @@ impl Retrier { }, || async { self.run().await }, |err, _| { - log::warn!("Retry error happened with {}. {}", self.tower_id, err); + log::warn!("Retry error happened with {}. {err}", self.tower_id); }, ) .await; @@ -392,7 +388,7 @@ impl Retrier { Err(e) => { // Notice we'll end up here after a permanent error. That is, either after finishing the backoff strategy // unsuccessfully or by manually raising such an error (like when facing a tower misbehavior). - log::warn!("Retry strategy gave up for {}. {}", self.tower_id, e); + log::warn!("Retry strategy gave up for {}. {e}", self.tower_id); if e.is_permanent() { self.set_status(RetrierStatus::Failed); } @@ -456,7 +452,7 @@ impl Retrier { let receipt = http::register(tower_id, user_id, &net_addr, &proxy) .await .map_err(|e| { - log::debug!("Cannot renew registration with tower. Error: {:?}", e); + log::debug!("Cannot renew registration with tower. Error: {e:?}"); Error::transient(RetryError::Subscription( "Cannot renew registration with tower".to_owned(), false, @@ -516,15 +512,14 @@ impl Retrier { AddAppointmentError::RequestError(e) => { if e.is_connection() { log::warn!( - "{} cannot be reached. Tower will be retried later", - tower_id, + "{tower_id} cannot be reached. Tower will be retried later" ); return Err(Error::transient(RetryError::Unreachable)); } } AddAppointmentError::ApiError(e) => match e.error_code { errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR => { - log::warn!("There is a subscription issue with {}", tower_id); + log::warn!("There is a subscription issue with {tower_id}"); self.wt_client .lock() .unwrap() @@ -536,8 +531,7 @@ impl Retrier { } _ => { log::warn!( - "{} rejected the appointment. Error: {}, error_code: {}", - tower_id, + "{tower_id} rejected the appointment. Error: {}, error_code: {}", e.error, e.error_code ); diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index d49fa7f..315d9aa 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -46,7 +46,7 @@ impl std::fmt::Debug for RevocationData { f, "{}", match self { - RevocationData::Fresh(l) => format!("Fresh: {}", l), + RevocationData::Fresh(l) => format!("Fresh: {l}"), RevocationData::Stale(hs) => format!( "Stale: {:?}", hs.iter().map(|l| l.to_string()).collect::>() @@ -90,7 +90,7 @@ impl WTClient { ) -> Self { // Create data dir if it does not exist fs::create_dir_all(&data_dir).await.unwrap_or_else(|e| { - log::error!("Cannot create data dir: {:?}", e); + log::error!("Cannot create data dir: {e:?}"); std::process::exit(1); }); @@ -120,10 +120,7 @@ impl WTClient { } } - log::info!( - "Plugin watchtower client initialized. User id = {}", - user_id - ); + log::info!("Plugin watchtower client initialized. User id = {user_id}"); WTClient { towers, @@ -206,14 +203,10 @@ impl WTClient { if tower.status != status { tower.status = status } else { - log::debug!("{} status is already {}", tower_id, status) + log::debug!("{tower_id} status is already {status}") } } else { - log::error!( - "Cannot change tower status to {}. Unknown tower_id: {}", - status, - tower_id - ); + log::error!("Cannot change tower status to {status}. Unknown tower_id: {tower_id}"); } } @@ -238,10 +231,7 @@ impl WTClient { .store_appointment_receipt(tower_id, locator, available_slots, receipt) .unwrap(); } else { - log::error!( - "Cannot add appointment receipt to tower. Unknown tower_id: {}", - tower_id - ); + log::error!("Cannot add appointment receipt to tower. Unknown tower_id: {tower_id}"); } } @@ -263,10 +253,7 @@ impl WTClient { .store_pending_appointment(tower_id, appointment) .unwrap(); } else { - log::error!( - "Cannot add pending appointment to tower. Unknown tower_id: {}", - tower_id - ); + log::error!("Cannot add pending appointment to tower. Unknown tower_id: {tower_id}"); } } @@ -279,10 +266,7 @@ impl WTClient { .delete_pending_appointment(tower_id, locator) .unwrap(); } else { - log::error!( - "Cannot remove pending appointment to tower. Unknown tower_id: {}", - tower_id - ); + log::error!("Cannot remove pending appointment to tower. Unknown tower_id: {tower_id}"); } } @@ -295,10 +279,7 @@ impl WTClient { .store_invalid_appointment(tower_id, appointment) .unwrap(); } else { - log::error!( - "Cannot add invalid appointment to tower. Unknown tower_id: {}", - tower_id - ); + log::error!("Cannot add invalid appointment to tower. Unknown tower_id: {tower_id}"); } } @@ -308,7 +289,7 @@ impl WTClient { self.dbm.store_misbehaving_proof(tower_id, &proof).unwrap(); tower.status = TowerStatus::Misbehaving; } else { - log::error!("Cannot flag tower. Unknown tower_id: {}", tower_id); + log::error!("Cannot flag tower. Unknown tower_id: {tower_id}"); } } From f628b358dbb2ed7f024fee4caf05ffa12a82c1e1 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 25 Jan 2023 12:18:06 -0500 Subject: [PATCH 081/119] Replaces Result for Option in DBM load methods The `DBM` methods related to loading data return `Result` where `E` is always `dbm::Error::NotFound`. It may makes more sense make them return `Option`. --- teos/src/chain_monitor.rs | 5 +- teos/src/dbm.rs | 85 ++++++++---------- teos/src/gatekeeper.rs | 6 +- teos/src/main.rs | 13 ++- teos/src/responder.rs | 18 +--- teos/src/watcher.rs | 135 +++++++++-------------------- watchtower-plugin/src/dbm.rs | 77 +++++++--------- watchtower-plugin/src/main.rs | 63 +++++++------- watchtower-plugin/src/wt_client.rs | 42 ++++----- 9 files changed, 170 insertions(+), 274 deletions(-) diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 0119267..3553401 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -264,10 +264,7 @@ mod tests { // If a new (worse, just one) block gets mined, nothing gets connected nor disconnected cm.poll_best_tip().await; assert_eq!(cm.last_known_block_header, best_tip); - assert!(matches!( - cm.dbm.lock().unwrap().load_last_known_block(), - Err { .. } - )); + assert!(cm.dbm.lock().unwrap().load_last_known_block().is_none()); assert!(listener.connected_blocks.borrow().is_empty()); assert!(listener.disconnected_blocks.borrow().is_empty()); } diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index af27214..4a53abd 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -274,7 +274,7 @@ impl DBM { } /// Loads an [Appointment] from the database. - pub(crate) fn load_appointment(&self, uuid: UUID) -> Result { + pub(crate) fn load_appointment(&self, uuid: UUID) -> Option { let key = uuid.to_vec(); let mut stmt = self .connection @@ -302,7 +302,7 @@ impl DBM { start_block, )) }) - .map_err(|_| Error::NotFound) + .ok() } /// Loads appointments from the database. If a locator is given, this method loads only the appointments @@ -406,7 +406,7 @@ impl DBM { } /// Loads the locator associated to a given UUID - pub(crate) fn load_locator(&self, uuid: UUID) -> Result { + pub(crate) fn load_locator(&self, uuid: UUID) -> Option { let mut stmt = self .connection .prepare("SELECT locator FROM appointments WHERE UUID=(?)") @@ -416,7 +416,7 @@ impl DBM { let raw_locator: Vec = row.get(0).unwrap(); Ok(Locator::from_slice(&raw_locator).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores a [TransactionTracker] into the database. @@ -451,7 +451,7 @@ impl DBM { } /// Loads a [TransactionTracker] from the database. - pub(crate) fn load_tracker(&self, uuid: UUID) -> Result { + pub(crate) fn load_tracker(&self, uuid: UUID) -> Option { let key = uuid.to_vec(); let mut stmt = self .connection.prepare( @@ -478,7 +478,7 @@ impl DBM { user_id, }) }) - .map_err(|_| Error::NotFound) + .ok() } /// Loads trackers from the database. If a locator is given, this method loads only the trackers @@ -537,7 +537,7 @@ impl DBM { } /// Loads the last known block from the database. - pub fn load_last_known_block(&self) -> Result { + pub fn load_last_known_block(&self) -> Option { let mut stmt = self .connection .prepare("SELECT block_hash FROM last_known_block WHERE id=0") @@ -547,7 +547,7 @@ impl DBM { let raw_hash: Vec = row.get(0).unwrap(); Ok(BlockHash::from_slice(&raw_hash).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores the tower secret key into the database. @@ -562,7 +562,7 @@ impl DBM { /// /// Loads the key with higher id from the database. Old keys are not overwritten just in case a recovery is needed, /// but they are not accessible from the API either. - pub fn load_tower_key(&self) -> Result { + pub fn load_tower_key(&self) -> Option { let mut stmt = self .connection .prepare( @@ -574,7 +574,7 @@ impl DBM { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } } @@ -602,7 +602,7 @@ mod tests { Ok(dbm) } - pub(crate) fn load_user(&self, user_id: UserId) -> Result { + pub(crate) fn load_user(&self, user_id: UserId) -> Option { let key = user_id.to_vec(); let mut stmt = self .connection @@ -611,21 +611,18 @@ mod tests { FROM users WHERE user_id=(?)", ) .unwrap(); - let user = stmt - .query_row([&key], |row| { - let slots = row.get(1).unwrap(); - let start = row.get(2).unwrap(); - let expiry = row.get(3).unwrap(); - Ok(UserInfo::with_appointments( - slots, - start, - expiry, - self.load_user_appointments(user_id), - )) - }) - .map_err(|_| Error::NotFound)?; - - Ok(user) + stmt.query_row([&key], |row| { + let slots = row.get(1).unwrap(); + let start = row.get(2).unwrap(); + let expiry = row.get(3).unwrap(); + Ok(UserInfo::with_appointments( + slots, + start, + expiry, + self.load_user_appointments(user_id), + )) + }) + .ok() } } @@ -680,7 +677,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let user_id = get_random_user_id(); - assert!(matches!(dbm.load_user(user_id), Err(Error::NotFound))); + assert!(dbm.load_user(user_id).is_none()); } #[test] @@ -779,11 +776,8 @@ mod tests { )); dbm.batch_remove_users(&HashSet::from_iter(vec![appointment.user_id])); - assert!(matches!( - dbm.load_user(appointment.user_id), - Err(Error::NotFound) - )); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_user(appointment.user_id).is_none()); + assert!(dbm.load_appointment(uuid).is_none()); // Appointment + Tracker dbm.store_user(appointment.user_id, &info).unwrap(); @@ -794,12 +788,9 @@ mod tests { assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); dbm.batch_remove_users(&HashSet::from_iter(vec![appointment.user_id])); - assert!(matches!( - dbm.load_user(appointment.user_id), - Err(Error::NotFound) - )); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_user(appointment.user_id).is_none()); + assert!(dbm.load_appointment(uuid).is_none()); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -846,7 +837,7 @@ mod tests { dbm.store_appointment(uuid, &appointment), Err(Error::MissingForeignKey) )); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!((dbm.load_tracker(uuid).is_none())); } #[test] @@ -854,7 +845,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let uuid = generate_uuid(); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); } #[test] @@ -1053,7 +1044,7 @@ mod tests { &HashSet::from_iter(vec![uuid]), &HashMap::from_iter([(appointment.user_id, info.clone())]), ); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); // Appointment + Tracker assert!(matches!( @@ -1066,8 +1057,8 @@ mod tests { &HashSet::from_iter(vec![uuid]), &HashMap::from_iter([(appointment.user_id, info)]), ); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -1103,7 +1094,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let (uuid, _) = generate_dummy_appointment_with_user(get_random_user_id(), None); - assert!(matches!(dbm.load_locator(uuid), Err(Error::NotFound))); + assert!(dbm.load_locator(uuid).is_none()); } #[test] @@ -1168,7 +1159,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let uuid = generate_uuid(); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -1251,14 +1242,14 @@ mod tests { fn test_store_load_nonexistent_last_known_block() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_last_known_block(), Err(Error::NotFound))); + assert!(dbm.load_last_known_block().is_none()); } #[test] fn test_store_load_tower_key() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_tower_key(), Err(Error::NotFound))); + assert!(dbm.load_tower_key().is_none()); for _ in 0..7 { let sk = get_random_keypair().0; dbm.store_tower_key(&sk).unwrap(); diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 7ae8a23..d306453 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -356,7 +356,6 @@ mod tests { }; use lightning::chain::Listen; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; - use teos_common::dbm::Error as DBError; use teos_common::test_utils::get_random_user_id; const SLOTS: u32 = 21; @@ -849,10 +848,7 @@ mod tests { .lock() .unwrap() .contains_key(user_id)); - assert!(matches!( - gatekeeper.dbm.lock().unwrap().load_user(*user_id), - Err(DBError::NotFound) - )); + assert!(gatekeeper.dbm.lock().unwrap().load_user(*user_id).is_none()); } // Check that the last_known_block_header has been properly updated diff --git a/teos/src/main.rs b/teos/src/main.rs index 97a882c..b7566b0 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -126,14 +126,11 @@ async fn main() { if conf.overwrite_key { log::info!("Overwriting tower keys"); create_new_tower_keypair(&locked_db) + } else if let Some(sk) = locked_db.load_tower_key() { + (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)) } else { - match locked_db.load_tower_key() { - Ok(sk) => (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)), - Err(_) => { - log::info!("Tower keys not found. Creating a fresh set"); - create_new_tower_keypair(&locked_db) - } - } + log::info!("Tower keys not found. Creating a fresh set"); + create_new_tower_keypair(&locked_db) } }; log::info!("tower_id: {tower_pk}"); @@ -179,7 +176,7 @@ async fn main() { let mut derefed = bitcoin_cli.deref(); // Load last known block from DB if found. Poll it from Bitcoind otherwise. let last_known_block = dbm.lock().unwrap().load_last_known_block(); - let tip = if let Ok(block_hash) = last_known_block { + let tip = if let Some(block_hash) = last_known_block { derefed .get_header(&block_hash, None) .await diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 0a0a1cd..38efcfc 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -297,7 +297,7 @@ impl Responder { /// The [TransactionTracker] is queried to the [DBM]. pub(crate) fn get_tracker(&self, uuid: UUID) -> Option { if self.trackers.lock().unwrap().contains_key(&uuid) { - self.dbm.lock().unwrap().load_tracker(uuid).ok() + self.dbm.lock().unwrap().load_tracker(uuid) } else { None } @@ -623,7 +623,6 @@ mod tests { }; use teos_common::constants::IRREVOCABLY_RESOLVED; - use teos_common::dbm::Error as DBError; use teos_common::test_utils::get_random_user_id; impl PartialEq for Responder { @@ -1581,10 +1580,7 @@ mod tests { assert!(!responder.tx_tracker_map.lock().unwrap().contains_key(&txid)); // But it can be found in the database - assert!(matches!( - responder.dbm.lock().unwrap().load_tracker(uuid), - Ok(TransactionTracker { .. }) - )); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_some()); } } @@ -1669,10 +1665,7 @@ mod tests { for uuid in all_trackers { if target_trackers.contains(&uuid) { assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(matches!( - responder.dbm.lock().unwrap().load_tracker(uuid), - Err(DBError::NotFound) - )); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); let penalty_txid = &uuid_txid_map[&uuid]; // If the penalty had more than one associated uuid, only one has been deleted // (because that's how the test has been designed) @@ -1702,10 +1695,7 @@ mod tests { .lock() .unwrap() .contains_key(&uuid_txid_map[&uuid])); - assert!(matches!( - responder.dbm.lock().unwrap().load_tracker(uuid), - Ok(TransactionTracker { .. }) - )); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_some()); } } diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index b94d6b2..ec2e7da 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -636,11 +636,10 @@ impl Watcher { Some(a) => locators.push(a.locator), None => { if self.responder.has_tracker(*uuid) { - match dbm.load_locator(*uuid) { - Ok(locator) => locators.push(locator), - Err(_) => log::error!( - "Tracker found in Responder but not in DB (uuid = {uuid})" - ), + if let Some(locator) = dbm.load_locator(*uuid) { + locators.push(locator) + } else { + log::error!("Tracker found in Responder but not in DB (uuid = {uuid})") } } else { log::error!("Appointment found in the Gatekeeper but not in the Watcher nor the Responder (uuid = {uuid})") @@ -770,7 +769,6 @@ mod tests { SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, }; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; - use teos_common::dbm::Error as DBError; use bitcoin::hash_types::Txid; use bitcoin::hashes::Hash; @@ -961,10 +959,12 @@ mod tests { // Check data was added to the database for uuid in watcher.appointments.lock().unwrap().keys() { - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(*uuid), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher + .dbm + .lock() + .unwrap() + .load_appointment(*uuid) + .is_some()); } // If an appointment is already in the Responder, it should bounce @@ -1010,14 +1010,8 @@ mod tests { assert!(watcher.responder.has_tracker(uuid)); // Check data was added to the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Ok(ExtendedAppointment { .. }) - )); - assert!(matches!( - watcher.dbm.lock().unwrap().load_tracker(uuid), - Ok(TransactionTracker { .. }) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); + assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_some()); // If an appointment is rejected by the Responder, it is considered misbehavior and the slot count is kept // Wrong penalty @@ -1034,14 +1028,8 @@ mod tests { assert_eq!(watcher.appointments.lock().unwrap().len(), 3); // Data should not be in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); - assert!(matches!( - watcher.dbm.lock().unwrap().load_tracker(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_none()); // Transaction rejected // Update the Responder with a new Carrier @@ -1062,10 +1050,7 @@ mod tests { assert_eq!(watcher.appointments.lock().unwrap().len(), 3); // Data should not be in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); // FAIL cases (non-registered, subscription expired and not enough slots) @@ -1078,10 +1063,7 @@ mod tests { Err(AddAppointmentFailure::AuthenticationFailure) )); // Data should not be in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); // If the user has no enough slots, the appointment is rejected. We do not test all possible cases since updates are // already tested int he Gatekeeper. Testing that it is rejected if the condition is met should suffice. @@ -1103,10 +1085,7 @@ mod tests { Err(AddAppointmentFailure::NotEnoughSlots) )); // Data should not be in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); // If the user subscription has expired, the appointment should be rejected. watcher @@ -1123,10 +1102,7 @@ mod tests { Err(AddAppointmentFailure::SubscriptionExpired { .. }) )); // Data should not be in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); } #[tokio::test] @@ -1211,10 +1187,7 @@ mod tests { ); // In this case the appointment is kept in the Responder and, therefore, in the database assert!(watcher.responder.has_tracker(uuid)); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); // A properly formatted but invalid transaction should be rejected by the Responder // Update the Responder with a new Carrier that will reject the transaction @@ -1232,10 +1205,7 @@ mod tests { ); // In this case the appointment is not kept in the Responder nor in the database assert!(!watcher.responder.has_tracker(uuid)); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err { .. } - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); // Invalid triggered appointments should not be passed to the Responder // Use a dispute_tx that does not match the appointment to replicate a decryption error @@ -1247,10 +1217,7 @@ mod tests { ); // The appointment is not kept anywhere assert!(!watcher.responder.has_tracker(uuid)); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err { .. } - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); } #[tokio::test] @@ -1502,10 +1469,7 @@ mod tests { .contains_key(&locator)); // But it can be found in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); } } @@ -1586,10 +1550,7 @@ mod tests { for uuid in all_appointments { if target_appointments.contains(&uuid) { assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); let locator = &uuid_locator_map[&uuid]; // If the penalty had more than one associated uuid, only one has been deleted @@ -1620,10 +1581,7 @@ mod tests { .lock() .unwrap() .contains_key(&uuid_locator_map[&uuid])); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); } } @@ -1728,10 +1686,12 @@ mod tests { .appointments .contains_key(&uuid1) ); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid1), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher + .dbm + .lock() + .unwrap() + .load_appointment(uuid1) + .is_some()); assert!(watcher.appointments.lock().unwrap().contains_key(&uuid2)); assert!(watcher.locator_uuid_map.lock().unwrap()[&appointment.locator()].contains(&uuid2)); @@ -1740,10 +1700,12 @@ mod tests { .appointments .contains_key(&uuid2) ); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid2), - Ok(ExtendedAppointment { .. }) - )); + assert!(watcher + .dbm + .lock() + .unwrap() + .load_appointment(uuid2) + .is_some()); // Check triggers. Add a new appointment and trigger it with valid data. let dispute_tx = get_random_tx(); @@ -1774,14 +1736,8 @@ mod tests { ); // Data should have been kept in the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Ok(ExtendedAppointment { .. }) - )); - assert!(matches!( - watcher.dbm.lock().unwrap().load_tracker(uuid), - Ok(TransactionTracker { .. }) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); + assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_some()); // Check triggering with a valid formatted transaction but that is rejected by the Responder. let dispute_tx = get_random_tx(); @@ -1816,14 +1772,8 @@ mod tests { .contains_key(&uuid) ); // Data should also have been deleted from the database - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); - assert!(matches!( - watcher.dbm.lock().unwrap().load_tracker(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_none()); // Checks invalid triggers. Add a new appointment and trigger it with invalid data. let dispute_tx = get_random_tx(); @@ -1855,10 +1805,7 @@ mod tests { .appointments .contains_key(&uuid) ); - assert!(matches!( - watcher.dbm.lock().unwrap().load_appointment(uuid), - Err(DBError::NotFound) - )); + assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); } #[tokio::test] diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index ec03886..bf271bb 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -125,7 +125,7 @@ impl DBM { /// /// Loads the key with higher id from the database. Old keys are not overwritten just in case a recovery is needed, /// but they are not accessible from the API either. - pub fn load_client_key(&self) -> Result { + pub fn load_client_key(&self) -> Option { let mut stmt = self .connection .prepare( @@ -137,7 +137,7 @@ impl DBM { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores a tower record into the database alongside the corresponding registration receipt. @@ -171,7 +171,7 @@ impl DBM { /// Tower records are composed from the tower information and the appointment data. The latter is split in: /// accepted appointments (represented by appointment receipts), pending appointments and invalid appointments. /// In the case that the tower has misbehaved, then a misbehaving proof is also attached to the record. - pub fn load_tower_record(&self, tower_id: TowerId) -> Result { + pub fn load_tower_record(&self, tower_id: TowerId) -> Option { let mut stmt = self .connection .prepare("SELECT t.net_addr, t.available_slots, r.subscription_start, r.subscription_expiry @@ -197,16 +197,16 @@ impl DBM { self.load_appointments(tower_id, AppointmentStatus::Invalid), )) }) - .map_err(|_| Error::NotFound)?; + .ok()?; - if let Ok(proof) = self.load_misbehaving_proof(tower_id) { + if let Some(proof) = self.load_misbehaving_proof(tower_id) { tower.status = TowerStatus::Misbehaving; tower.set_misbehaving_proof(proof); } else if !tower.pending_appointments.is_empty() { tower.status = TowerStatus::TemporaryUnreachable; } - Ok(tower) + Some(tower) } /// Loads the latest registration receipt for a given tower. @@ -216,7 +216,7 @@ impl DBM { &self, tower_id: TowerId, user_id: UserId, - ) -> Result { + ) -> Option { let mut stmt = self .connection .prepare( @@ -228,20 +228,17 @@ impl DBM { ) .unwrap(); - let receipt = stmt - .query_row([tower_id.to_vec()], |row| { - let slots: u32 = row.get(0).unwrap(); - let start: u32 = row.get(1).unwrap(); - let expiry: u32 = row.get(2).unwrap(); - let signature: String = row.get(3).unwrap(); + stmt.query_row([tower_id.to_vec()], |row| { + let slots: u32 = row.get(0).unwrap(); + let start: u32 = row.get(1).unwrap(); + let expiry: u32 = row.get(2).unwrap(); + let signature: String = row.get(3).unwrap(); - Ok(RegistrationReceipt::with_signature( - user_id, slots, start, expiry, signature, - )) - }) - .map_err(|_| Error::NotFound)?; - - Ok(receipt) + Ok(RegistrationReceipt::with_signature( + user_id, slots, start, expiry, signature, + )) + }) + .ok() } /// Removes a tower record from the database. @@ -333,7 +330,7 @@ impl DBM { &self, tower_id: TowerId, locator: Locator, - ) -> Result { + ) -> Option { let mut stmt = self .connection .prepare("SELECT start_block, user_signature, tower_signature FROM appointment_receipts WHERE tower_id = ?1 and locator = ?2") @@ -350,7 +347,7 @@ impl DBM { tower_sig, )) }) - .map_err(|_| Error::NotFound) + .ok() } /// Loads the appointment receipts associated to a given tower. @@ -406,7 +403,7 @@ impl DBM { } /// Loads an appointment from the database. - pub fn load_appointment(&self, locator: Locator) -> Result { + pub fn load_appointment(&self, locator: Locator) -> Option { let mut stmt = self .connection .prepare("SELECT encrypted_blob, to_self_delay FROM appointments WHERE locator = ?") @@ -418,7 +415,7 @@ impl DBM { Ok(Appointment::new(locator, encrypted_blob, to_self_delay)) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores an appointment into the database. @@ -598,7 +595,7 @@ impl DBM { } /// Loads the misbehaving proof for a given tower from the database (if found). - fn load_misbehaving_proof(&self, tower_id: TowerId) -> Result { + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { let mut misbehaving_stmt = self .connection .prepare("SELECT locator, recovered_id FROM misbehaving_proofs WHERE tower_id = ?") @@ -633,7 +630,7 @@ impl DBM { .unwrap(); MisbehaviorProof::new(locator, receipt, recovered_id) }) - .map_err(|_| Error::NotFound) + .ok() } /// Checks whether a misbehaving proof exists for a given tower. @@ -792,10 +789,7 @@ mod tests { // If the tower does not exists, `load_tower` will fail. let tower_id = get_random_user_id(); - assert!(matches!( - dbm.load_tower_record(tower_id), - Err(Error::NotFound) - )); + assert!(dbm.load_tower_record(tower_id).is_none()); } #[test] @@ -917,10 +911,9 @@ mod tests { // If there is no appointment receipt for the given (locator, tower_id) pair, Error::NotFound is returned // Try first with both being unknown - assert!(matches!( - dbm.load_appointment_receipt(tower_id, appointment.locator), - Err(Error::NotFound) - )); + assert!(dbm + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); // Add the tower but not the appointment and try again let net_addr = "talaia.watch"; @@ -928,10 +921,9 @@ mod tests { dbm.store_tower_record(tower_id, net_addr, &receipt) .unwrap(); - assert!(matches!( - dbm.load_appointment_receipt(tower_id, appointment.locator), - Err(Error::NotFound) - )); + assert!(dbm + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); // Add both let tower_summary = TowerSummary::new( @@ -1045,7 +1037,7 @@ mod tests { let locator = generate_random_appointment(None).locator; let loaded_appointment = dbm.load_appointment(locator); - assert!(matches!(loaded_appointment, Err(Error::NotFound))); + assert!(loaded_appointment.is_none()); } #[test] @@ -1284,10 +1276,7 @@ mod tests { #[test] fn test_store_load_non_existing_misbehaving_proof() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!( - dbm.load_misbehaving_proof(get_random_user_id()), - Err(Error::NotFound) - )); + assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); } #[test] @@ -1340,7 +1329,7 @@ mod tests { fn test_store_load_client_key() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_client_key(), Err(Error::NotFound))); + assert!(dbm.load_client_key().is_none()); for _ in 0..7 { let sk = get_random_keypair().0; dbm.store_client_key(&sk).unwrap(); diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f7c0fbf..3d4910c 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -136,11 +136,13 @@ async fn get_registration_receipt( let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; let state = plugin.state().lock().unwrap(); - let response = state.get_registration_receipt(tower_id).map_err(|_| { - anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") - })?; - - Ok(json!(response)) + if let Some(response) = state.get_registration_receipt(tower_id) { + Ok(json!(response)) + } else { + Err(anyhow!( + "Cannot find {tower_id} within the known towers. Have you registered?" + )) + } } /// Gets the subscription information directly form the tower. @@ -244,24 +246,20 @@ async fn get_appointment_receipt( let params = GetAppointmentParams::try_from(v).map_err(|x| anyhow!(x))?; let state = plugin.state().lock().unwrap(); - let response = state - .get_appointment_receipt(params.tower_id, params.locator) - .map_err(|_| { - if state.towers.contains_key(¶ms.tower_id) { - anyhow!( - "Cannot find {} within {}. Did you send that appointment?", - params.locator, - params.tower_id - ) - } else { - anyhow!( - "Cannot find {} within the known towers. Have you registered?", - params.tower_id - ) - } - })?; - - Ok(json!(response)) + if let Some(r) = state.get_appointment_receipt(params.tower_id, params.locator) { + Ok(json!(r)) + } else if state.towers.contains_key(¶ms.tower_id) { + Err(anyhow!( + "Cannot find {} within {}. Did you send that appointment?", + params.locator, + params.tower_id + )) + } else { + Err(anyhow!( + "Cannot find {} within the known towers. Have you registered?", + params.tower_id + )) + } } /// Lists all the registered towers. @@ -283,15 +281,18 @@ async fn get_tower_info( ) -> Result { let state = plugin.state().lock().unwrap(); let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; - let tower_info = state.load_tower_info(tower_id).map_err(|_| { - anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") - })?; - // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable - // by just checking the data in the database. - Ok(json!( - tower_info.with_status(state.get_tower_status(&tower_id).unwrap()) - )) + if let Some(tower_info) = state.load_tower_info(tower_id) { + // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable + // by just checking the data in the database. + Ok(json!( + tower_info.with_status(state.get_tower_status(&tower_id).unwrap()) + )) + } else { + Err(anyhow!( + "Cannot find {tower_id} within the known towers. Have you registered?", + )) + } } /// Triggers a manual retry of a tower, tries to send all pending appointments to it. diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 315d9aa..ecf26db 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -95,17 +95,17 @@ impl WTClient { }); let dbm = DBM::new(&data_dir.join("watchtowers_db.sql3")).unwrap(); - let (user_sk, user_id) = match dbm.load_client_key() { - Ok(sk) => ( + + let (user_sk, user_id) = if let Some(sk) = dbm.load_client_key() { + ( sk, UserId(PublicKey::from_secret_key(&Secp256k1::new(), &sk)), - ), - Err(_) => { - log::info!("Watchtower client keys not found. Creating a fresh set"); - let (sk, pk) = cryptography::get_random_keypair(); - dbm.store_client_key(&sk).unwrap(); - (sk, UserId(pk)) - } + ) + } else { + log::info!("Watchtower client keys not found. Creating a fresh set"); + let (sk, pk) = cryptography::get_random_keypair(); + dbm.store_client_key(&sk).unwrap(); + (sk, UserId(pk)) }; let towers = dbm.load_towers(); @@ -180,15 +180,12 @@ impl WTClient { } /// Gets the latest registration receipt of a given tower. - pub fn get_registration_receipt( - &self, - tower_id: TowerId, - ) -> Result { + pub fn get_registration_receipt(&self, tower_id: TowerId) -> Option { self.dbm.load_registration_receipt(tower_id, self.user_id) } /// Loads a tower record from the database. - pub fn load_tower_info(&self, tower_id: TowerId) -> Result { + pub fn load_tower_info(&self, tower_id: TowerId) -> Option { self.dbm.load_tower_record(tower_id) } @@ -240,7 +237,7 @@ impl WTClient { &self, tower_id: TowerId, locator: Locator, - ) -> Result { + ) -> Option { self.dbm.load_appointment_receipt(tower_id, locator) } @@ -810,10 +807,7 @@ mod tests { // Remove the tower and check it is not there anymore wt_client.remove_tower(tower_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); // Try again but this time with an associated appointment to check that it also gets removed @@ -836,10 +830,7 @@ mod tests { // Remove and check both the tower and the appointment wt_client.remove_tower(tower_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower_id)); } @@ -890,10 +881,7 @@ mod tests { // Remove tower1 and check that the appointment receipt can still be found for tower2 wt_client.remove_tower(tower1_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower1_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower1_id).is_none()); assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower1_id)); assert!(wt_client.dbm.appointment_receipt_exists(locator, tower2_id)); From f63fd14184ec31a71fadb75fa2206fbd38cc8593 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 15 Feb 2023 12:45:19 +0100 Subject: [PATCH 082/119] Removes WIP, adds social, build and version badges --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4213e2d..9b5f754 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -**THIS IS CURRENTLY WIP** - # The Eye of Satoshi (rust-teos) The Eye of Satoshi is a Lightning watchtower compliant with [BOLT13](https://github.com/sr-gi/bolt13), written in Rust. +[![discord](https://img.shields.io/discord/991334710611550208?logo=discord&style=plastic)](https://discord.gg/EyVbrNMDUP) +[![build](https://img.shields.io/github/actions/workflow/status/talaia-labs/rust-teos/build.yaml?logo=github&style=plastic)](https://github.com/talaia-labs/rust-teos/actions/workflows/build.yaml) +[![release](https://img.shields.io/github/v/release/talaia-labs/rust-teos?style=plastic)](https://github.com/talaia-labs/rust-teos/releases/latest) + + `rust-teos` consists of two main crates: - `teos`: including the tower's main functionality (server-side) and a CLI. Compiling this crate will generate two binaries: `teosd` and `teos-cli`. From 939d87c0f0b4080c82dff2d925e8dd2cd5d5484f Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 24 Feb 2023 22:32:53 +0100 Subject: [PATCH 083/119] Makes http::serve take a SocketAddr instead of a String `PublicTowerServicesClient::connect` needs `dest` to be `std::convert::TryInto` hence why we were passing a `String`. However, we'll be better of passing something that's `Copy`, like `SocketAddr` and converting to `String` here. --- teos/src/api/http.rs | 4 ++-- teos/src/main.rs | 20 ++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 385c5d7..2655c32 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -290,12 +290,12 @@ async fn handle_rejection(err: Rejection) -> Result { pub async fn serve( http_bind: SocketAddr, - grpc_bind: String, + grpc_bind: SocketAddr, service_ready: Trigger, shutdown_signal: Listener, ) { let grpc_conn = loop { - match PublicTowerServicesClient::connect(grpc_bind.clone()).await { + match PublicTowerServicesClient::connect(format!("http://{grpc_bind}")).await { Ok(conn) => break conn, Err(_) => { log::error!("Cannot connect to the gRPC server. Retrying shortly"); diff --git a/teos/src/main.rs b/teos/src/main.rs index b7566b0..3e61247 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -245,7 +245,7 @@ async fn main() { } let (shutdown_trigger, shutdown_signal_rpc_api) = triggered::trigger(); - let shutdown_signal_internal_rpc_api = shutdown_signal_rpc_api.clone(); + let shutdown_signal_internal_api = shutdown_signal_rpc_api.clone(); let shutdown_signal_http = shutdown_signal_rpc_api.clone(); let shutdown_signal_cm = shutdown_signal_rpc_api.clone(); let shutdown_signal_tor = shutdown_signal_rpc_api.clone(); @@ -297,24 +297,20 @@ async fn main() { None }; - let rpc_api = Arc::new(InternalAPI::new( + let internal_api = Arc::new(InternalAPI::new( watcher, addresses, bitcoind_reachable.clone(), shutdown_trigger, )); - let internal_rpc_api = rpc_api.clone(); + let internal_api_cloned = internal_api.clone(); let rpc_api_addr = format!("{}:{}", conf.rpc_bind, conf.rpc_port) .parse() .unwrap(); - let internal_rpc_api_addr = format!("{}:{}", conf.internal_api_bind, conf.internal_api_port) + let internal_api_addr = format!("{}:{}", conf.internal_api_bind, conf.internal_api_port) .parse() .unwrap(); - let internal_rpc_api_uri = format!( - "http://{}:{}", - conf.internal_api_bind, conf.internal_api_port - ); // Generate mtls certificates to data directory so the admin can securely connect // to the server to perform administrative tasks. @@ -332,7 +328,7 @@ async fn main() { Server::builder() .tls_config(tls) .expect("couldn't configure tls") - .add_service(PrivateTowerServicesServer::new(rpc_api)) + .add_service(PrivateTowerServicesServer::new(internal_api)) .serve_with_shutdown(rpc_api_addr, shutdown_signal_rpc_api) .await .unwrap(); @@ -340,8 +336,8 @@ async fn main() { let public_api_task = task::spawn(async move { Server::builder() - .add_service(PublicTowerServicesServer::new(internal_rpc_api)) - .serve_with_shutdown(internal_rpc_api_addr, shutdown_signal_internal_rpc_api) + .add_service(PublicTowerServicesServer::new(internal_api_cloned)) + .serve_with_shutdown(internal_api_addr, shutdown_signal_internal_api) .await .unwrap(); }); @@ -349,7 +345,7 @@ async fn main() { let (http_service_ready, ready_signal_http) = triggered::trigger(); let http_api_task = task::spawn(http::serve( http_api_addr, - internal_rpc_api_uri, + internal_api_addr, http_service_ready, shutdown_signal_http, )); From 7dc3fcd2cc731e4761cf9e4120da17879127ec90 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 20 Feb 2023 17:29:04 +0100 Subject: [PATCH 084/119] Replaces httpmock with mockito Also: - Replaced time based waits for loops with the expected conditions to get given the former is way more error prone. - Updates tests that needed a response based on the request (main reason why were're switching to mockito) --- Cargo.lock | 754 +++++------------------------- watchtower-plugin/Cargo.toml | 2 +- watchtower-plugin/src/net/http.rs | 167 +++---- watchtower-plugin/src/retrier.rs | 433 +++++++++-------- 4 files changed, 440 insertions(+), 916 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a9edf9..9302093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,15 +46,6 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -65,128 +56,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-channel" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" -dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2 0.4.4", - "waker-fn", - "winapi 0.3.9", -] - -[[package]] -name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-object-pool" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" -dependencies = [ - "async-std", -] - -[[package]] -name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" -dependencies = [ - "async-io", - "blocking", - "cfg-if 1.0.0", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi 0.3.9", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite 0.2.8", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.2" @@ -208,12 +77,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - [[package]] name = "async-trait" version = "0.1.52" @@ -225,12 +88,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - [[package]] name = "atty" version = "0.2.14" @@ -259,7 +116,7 @@ dependencies = [ "instant", "pin-project-lite 0.2.8", "rand 0.8.5", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -283,38 +140,12 @@ dependencies = [ "byteorder", ] -[[package]] -name = "basic-cookies" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" -dependencies = [ - "lalrpop", - "lalrpop-util", - "regex", -] - [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitcoin" version = "0.28.1" @@ -392,20 +223,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - [[package]] name = "bstr" version = "0.2.17" @@ -449,18 +266,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" version = "1.0.73" @@ -557,7 +362,7 @@ dependencies = [ "log", "serde", "serde_json", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "tokio-util 0.7.0", ] @@ -573,15 +378,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -613,22 +409,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" -dependencies = [ - "cfg-if 1.0.0", - "once_cell", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.3" @@ -649,47 +429,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ctor" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "curl" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.4.4", - "winapi 0.3.9", -] - -[[package]] -name = "curl-sys" -version = "0.4.55+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi 0.3.9", -] - [[package]] name = "curve25519-dalek" version = "3.2.1" @@ -709,6 +448,25 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "deadpool" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +dependencies = [ + "async-trait", + "deadpool-runtime", + "num_cpus", + "retain_mut", + "tokio 1.25.0", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" + [[package]] name = "der-oid-macro" version = "0.5.0" @@ -746,12 +504,6 @@ dependencies = [ "syn", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.9.0" @@ -771,27 +523,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - [[package]] name = "ed25519" version = "1.5.2" @@ -821,15 +552,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "ena" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" -dependencies = [ - "log", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -873,12 +595,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1007,21 +723,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite 0.2.8", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.21" @@ -1108,18 +809,6 @@ dependencies = [ "regex", ] -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.2.7" @@ -1154,7 +843,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-util 0.6.9", "tracing", ] @@ -1307,34 +996,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "httpmock" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c159c4fc205e6c1a9b325cb7ec135d13b5f47188ce175dabb76ec847f331d9bd" -dependencies = [ - "assert-json-diff", - "async-object-pool", - "async-trait", - "base64", - "basic-cookies", - "crossbeam-utils", - "form_urlencoded", - "futures-util", - "hyper 0.14.18", - "isahc", - "lazy_static", - "levenshtein", - "log", - "regex", - "serde", - "serde_json", - "serde_regex", - "similar", - "tokio 1.20.1", - "url", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1383,7 +1044,7 @@ dependencies = [ "itoa 1.0.1", "pin-project-lite 0.2.8", "socket2 0.4.4", - "tokio 1.20.1", + "tokio 1.25.0", "tower-service", "tracing", "want", @@ -1397,7 +1058,7 @@ checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper 0.14.18", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-io-timeout", ] @@ -1410,7 +1071,7 @@ dependencies = [ "bytes 1.1.0", "hyper 0.14.18", "native-tls", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-native-tls", ] @@ -1481,33 +1142,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.10.3" @@ -1577,7 +1211,7 @@ dependencies = [ "jsonrpc-server-utils", "log", "net2", - "parking_lot", + "parking_lot 0.11.2", "unicase", ] @@ -1614,75 +1248,18 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lalrpop" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" -dependencies = [ - "ascii-canvas", - "atty", - "bit-set", - "diff", - "ena", - "itertools", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" -dependencies = [ - "regex", -] - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "libsqlite3-sys" version = "0.23.2" @@ -1694,18 +1271,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "lightning" version = "0.0.108" @@ -1737,7 +1302,7 @@ checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" dependencies = [ "bitcoin", "lightning", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -1762,7 +1327,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if 1.0.0", - "value-bag", ] [[package]] @@ -1842,6 +1406,28 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "mockito" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa08cccbc31b07113e4322d79df4464e0ed888032fc67ec56136325cd3afecf" +dependencies = [ + "assert-json-diff", + "async-trait", + "colored", + "deadpool", + "futures", + "hyper 0.14.18", + "lazy_static", + "log", + "rand 0.8.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio 1.25.0", +] + [[package]] name = "multimap" version = "0.8.3" @@ -1895,12 +1481,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nom" version = "7.1.1" @@ -2026,12 +1606,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "parking_lot" version = "0.11.2" @@ -2040,7 +1614,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.6", ] [[package]] @@ -2057,6 +1641,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + [[package]] name = "pem" version = "1.1.0" @@ -2082,21 +1679,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - [[package]] name = "pin-project" version = "1.0.10" @@ -2141,19 +1723,6 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "log", - "wepoll-ffi", - "winapi 0.3.9", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -2171,12 +1740,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2431,21 +1994,11 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom 0.2.5", - "redox_syscall", -] - [[package]] name = "regex" -version = "1.5.6" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -2454,9 +2007,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -2494,7 +2047,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2505,6 +2058,12 @@ dependencies = [ "winreg", ] +[[package]] +name = "retain_mut" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" + [[package]] name = "ring" version = "0.16.20" @@ -2580,12 +2139,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustversion" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" - [[package]] name = "ryu" version = "1.0.9" @@ -2710,16 +2263,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2781,16 +2324,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2808,9 +2341,9 @@ checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" [[package]] name = "similar" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "simple_logger" @@ -2825,29 +2358,12 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.8.0" @@ -2881,19 +2397,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "string_cache" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.8.0" @@ -3000,7 +2503,7 @@ dependencies = [ "structopt", "tempdir", "teos-common", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "toml", "tonic 0.6.2", @@ -3027,17 +2530,6 @@ dependencies = [ "tonic-build", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -3094,15 +2586,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinyvec" version = "1.5.1" @@ -3138,9 +2621,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.20.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes 1.1.0", @@ -3148,12 +2631,12 @@ dependencies = [ "memchr", "mio 0.8.4", "num_cpus", - "once_cell", + "parking_lot 0.12.1", "pin-project-lite 0.2.8", "signal-hook-registry", "socket2 0.4.4", "tokio-macros", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -3163,7 +2646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3184,7 +2667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3194,7 +2677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.20.1", + "tokio 1.25.0", "webpki", ] @@ -3207,7 +2690,7 @@ dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3218,7 +2701,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3230,7 +2713,7 @@ dependencies = [ "futures-util", "log", "pin-project", - "tokio 1.20.1", + "tokio 1.25.0", "tungstenite", ] @@ -3259,7 +2742,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3273,7 +2756,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3306,7 +2789,7 @@ dependencies = [ "pin-project", "prost 0.8.0", "prost-derive 0.8.0", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-rustls", "tokio-stream", "tokio-util 0.6.9", @@ -3338,7 +2821,7 @@ dependencies = [ "pin-project", "prost 0.9.0", "prost-derive 0.9.0", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-rustls", "tokio-stream", "tokio-util 0.6.9", @@ -3378,7 +2861,7 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3394,7 +2877,7 @@ dependencies = [ "pin-project-lite 0.2.8", "rand 0.8.5", "slab", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-util 0.7.0", "tower-layer", "tower-service", @@ -3584,16 +3067,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "value-bag" -version = "1.0.0-alpha.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -3612,12 +3085,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "want" version = "0.3.0" @@ -3650,7 +3117,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "tokio-tungstenite", "tokio-util 0.6.9", @@ -3751,15 +3218,15 @@ dependencies = [ "cln-plugin", "hex", "home", - "httpmock", "log", + "mockito", "reqwest", "rusqlite", "serde", "serde_json", "tempdir", "teos-common", - "tokio 1.20.1", + "tokio 1.25.0", "tonic 0.5.2", ] @@ -3783,15 +3250,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "which" version = "4.2.4" diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 199cb8f..976f8e9 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -32,5 +32,5 @@ cln-plugin = "0.1.2" teos-common = { path = "../teos-common" } [dev-dependencies] -httpmock = "0.6" +mockito = "0.32.4" tempdir = "0.3.7" \ No newline at end of file diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 23448a5..b789b02 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -218,7 +218,6 @@ pub async fn process_post_response( #[cfg(test)] mod tests { use super::*; - use httpmock::prelude::*; use serde_json::json; use crate::test_utils::get_dummy_add_appointment_response; @@ -253,24 +252,25 @@ mod tests { let mut registration_receipt = get_random_registration_receipt(); registration_receipt.sign(&tower_sk); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(registration_receipt)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(registration_receipt).to_string()) + .create_async() + .await; let receipt = register( TowerId(tower_pk), registration_receipt.user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, ) .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(receipt, registration_receipt); } @@ -290,24 +290,25 @@ mod tests { #[tokio::test] async fn test_register_deserialize_error() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!([])); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!([]).to_string()) + .create_async() + .await; let error = register( get_random_user_id(), get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, ) .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, RequestError::DeserializeError { .. })) } @@ -322,17 +323,18 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let (response, receipt) = add_appointment( TowerId(tower_pk), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -340,7 +342,7 @@ mod tests { .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(response, add_appointment_response.available_slots); assert_eq!(receipt, appointment_receipt); } @@ -354,17 +356,18 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let (response, receipt) = send_appointment( TowerId(tower_pk), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -372,7 +375,7 @@ mod tests { .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(response, add_appointment_response); assert_eq!(receipt, appointment_receipt); } @@ -386,18 +389,19 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let tower_id = get_random_user_id(); let error = send_appointment( tower_id, - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -405,7 +409,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; if let AddAppointmentError::SignatureError(proof) = error { assert_eq!( MisbehaviorProof::new( @@ -441,17 +445,18 @@ mod tests { #[tokio::test] async fn test_send_appointment_deserialize_error() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!([])); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!([]).to_string()) + .create_async() + .await; let error = send_appointment( get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &generate_random_appointment(None), "user_sig", @@ -459,7 +464,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; if let AddAppointmentError::RequestError(e) = error { assert!(matches!(e, RequestError::DeserializeError { .. })) } else { @@ -474,17 +479,18 @@ mod tests { error_code: 1, }; - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(api_error)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body(json!(api_error).to_string()) + .create_async() + .await; let error = send_appointment( get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &generate_random_appointment(None), "user_sig", @@ -492,20 +498,22 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, AddAppointmentError::ApiError { .. })); } #[tokio::test] async fn test_post_request() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST); - then.status(200).header("content-type", "application/json"); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; let response = post_request( - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), Endpoint::Register, json!(""), &None, @@ -513,7 +521,7 @@ mod tests { .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(response, Response { .. })); } @@ -536,16 +544,19 @@ mod tests { async fn test_process_post_response_json_error() { // `process_post_response` is a pass-trough function that maps json deserialization errors from `post_request`. // So just testing that specific case should be enough. - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST); - then.status(200).header("content-type", "application/json"); - }); + + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::GetAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( post_request( - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), Endpoint::GetAppointment, json!(""), &None, @@ -555,7 +566,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, RequestError::DeserializeError { .. })); } } diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index b980a40..0920e36 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -576,13 +576,13 @@ impl Retrier { mod tests { use super::*; - use httpmock::prelude::*; use serde_json::json; use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; use teos_common::net::http::Endpoint; + use teos_common::protos::AddAppointmentRequest; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, @@ -600,6 +600,18 @@ mod tests { const MAX_INTERVAL_TIME: u16 = 1; const MAX_RUN_TIME: f64 = 0.2; + macro_rules! wait_until { + () => {}; + ($cond:expr $(,)?) => { + loop { + if $cond { + break; + } + tokio::time::sleep(Duration::from_secs_f64(0.1)).await; + } + }; + } + impl Retrier { fn empty(wt_client: Arc>, tower_id: TowerId) -> Self { Self { @@ -612,15 +624,14 @@ mod tests { } #[tokio::test] - // TODO: It'll be nice to toggle the mock on and off instead of having it always on. Not sure MockServer allows that though: - // https://github.com/alexliesenfeld/httpmock/issues/67 async fn test_manage_retry_reachable() { let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -629,7 +640,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -647,13 +658,17 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(add_appointment_response).to_string().into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -681,23 +696,23 @@ mod tests { .unwrap() .is_running()); - // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - - let state = wt_client.lock().unwrap(); - assert_eq!( - state.get_tower_status(&tower_id).unwrap(), - TowerStatus::Reachable - ); - assert!(!state.retriers.contains_key(&tower_id)); - assert!(!state - .towers - .get(&tower_id) + wait_until!(wt_client + .lock() .unwrap() - .pending_appointments - .contains(&appointment.locator)); + .get_retrier_status(&tower_id) + .is_none()); - api_mock.assert(); + { + let state = wt_client.lock().unwrap(); + assert!(state.get_tower_status(&tower_id).unwrap().is_reachable()); + assert!(!state + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + } + api_mock.assert_async().await; task.abort(); } @@ -760,25 +775,23 @@ mod tests { .is_running()); // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, so the retiers will be idle). - // Notice we'd normally wait for MAX_ELAPSED_TIME + MAX_RUN_TIME (the maximum time a Retrier can be working plus the marginal time of the last retry). - // However, we've already waited for MAX_RUN_TIME right before to check the tower was temporary unreachable, so we don't need to account for that again. - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; - assert!(wt_client - .lock() - .unwrap() - .get_tower_status(&tower_id) - .unwrap() - .is_unreachable()); - assert!(wt_client + wait_until!(wt_client .lock() .unwrap() .get_retrier_status(&tower_id) .unwrap() .is_idle()); + assert!(wt_client + .lock() + .unwrap() + .get_tower_status(&tower_id) + .unwrap() + .is_unreachable()); + // Add a proper server and check that the auto-retry works // Prepare the mock response - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; let mut add_appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, @@ -786,12 +799,13 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Update the tower details wt_client @@ -799,7 +813,7 @@ mod tests { .unwrap() .add_update_tower( tower_id, - &server.base_url(), + &server.url(), &get_registration_receipt_from_previous(&receipt), ) .unwrap(); @@ -824,8 +838,7 @@ mod tests { .pending_appointments .contains(&appointment.locator)); assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -837,7 +850,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -846,7 +859,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -857,16 +870,21 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Prepare the mock response - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body_from_request(|_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(ApiError { error: "error_msg".to_owned(), error_code: 1, - })); - }); + }) + .to_string() + .into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -895,16 +913,18 @@ mod tests { .is_running()); // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - assert_eq!( - wt_client - .lock() - .unwrap() - .get_tower_status(&tower_id) - .unwrap(), - TowerStatus::Reachable - ); - assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + assert!(wt_client + .lock() + .unwrap() + .get_tower_status(&tower_id) + .unwrap() + .is_reachable()); assert!(!wt_client .lock() .unwrap() @@ -921,7 +941,7 @@ mod tests { .unwrap() .invalid_appointments .contains(&appointment.locator)); - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -933,7 +953,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -942,7 +962,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -961,13 +981,16 @@ mod tests { add_appointment_receipt.sign(&cryptography::get_random_keypair().0); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(add_appointment_response).to_string().into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -995,19 +1018,21 @@ mod tests { .unwrap() .is_running()); - // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY + MAX_RUN_TIME)).await; + // Wait until the tower is no longer being retried. + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + // The tower should have a misbehaving status. assert!(wt_client .lock() .unwrap() .get_tower_status(&tower_id) .unwrap() .is_misbehaving()); - - // Retriers are wiped every polling interval, so we'll need to wait a bit more to check it - tokio::time::sleep(Duration::from_secs(POLLING_TIME)).await; - assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -1019,7 +1044,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -1028,7 +1053,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) @@ -1049,7 +1074,6 @@ mod tests { .manage_retry() .await }); - assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); task.abort(); @@ -1062,7 +1086,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -1073,7 +1097,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), ®istration_receipt) + .add_update_tower(tower_id, &server.url(), ®istration_receipt) .unwrap(); // Add appointment to pending @@ -1083,7 +1107,11 @@ mod tests { .unwrap() .add_pending_appointment(tower_id, &appointment); - // Mock the add_appointment response (this is right, so after the re-registration the appointments are accepted) + // Mock the registration and add_appointment response (this is right, so after the re-registration the appointments are accepted) + let mut re_registration_receipt = + get_registration_receipt_from_previous(®istration_receipt); + re_registration_receipt.sign(&tower_sk); + let mut add_appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, @@ -1091,24 +1119,25 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let add_appointment_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); - // Mock the re-registration - let mut re_registration_receipt = - get_registration_receipt_from_previous(®istration_receipt); - re_registration_receipt.sign(&tower_sk); - let register_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(re_registration_receipt)); - }); + let api_mock = server + .mock("POST", mockito::Matcher::Any) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |request| { + let response = if request.path() == Endpoint::Register.path().as_str() { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(re_registration_receipt).to_string() + } else if request.path() == Endpoint::AddAppointment.path().as_str() { + json!(add_appointment_response).to_string() + } else { + panic!("Wrong endpoint hit") + }; + response.into() + }) + .create_async() + .await + .expect(2); // Set the status as SubscriptionError so we simulate the retrier faced this in a previous round wt_client @@ -1143,16 +1172,20 @@ mod tests { .is_running()); // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - let state = wt_client.lock().unwrap(); - assert!(!state.retriers.contains_key(&tower_id)); + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); - let tower = state.towers.get(&tower_id).unwrap(); - assert!(tower.status.is_reachable()); - assert!(tower.pending_appointments.is_empty()); + { + let state = wt_client.lock().unwrap(); + let tower = state.towers.get(&tower_id).unwrap(); + assert!(tower.status.is_reachable()); + assert!(tower.pending_appointments.is_empty()); + } + api_mock.assert_async().await; - register_mock.assert(); - add_appointment_mock.assert(); task.abort(); } @@ -1216,12 +1249,12 @@ mod tests { // With the retrier idling all fresh data sent to it will be stored but it won't trigger a retry. // (we can check the data was stored later on) - let new_appointment = generate_random_appointment(None); + let appointment2 = generate_random_appointment(None); wt_client .lock() .unwrap() - .add_pending_appointment(tower_id, &new_appointment); - tx.send((tower_id, RevocationData::Fresh(new_appointment.locator))) + .add_pending_appointment(tower_id, &appointment2); + tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) .unwrap(); { @@ -1232,22 +1265,40 @@ mod tests { assert_eq!(tower.status, TowerStatus::Unreachable); } - let mut add_appointment_receipt = AppointmentReceipt::new( + // Create the receipts, the responses and set the mocks + let mut appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, ); + let mut appointment2_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment2.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + appointment_receipt.sign(&tower_sk); + appointment2_receipt.sign(&tower_sk); // Mock a proper response - let server = MockServer::start(); - add_appointment_receipt.sign(&tower_sk); - let add_appointment_response = - get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |request| { + let body = serde_json::from_slice::(request.body().unwrap()) + .unwrap(); + + let response = if body.appointment.unwrap().locator == appointment.locator.to_vec() + { + get_dummy_add_appointment_response(appointment.locator, &appointment_receipt) + } else { + get_dummy_add_appointment_response(appointment2.locator, &appointment2_receipt) + }; + json!(response).to_string().into() + }) + .expect(2) + .create_async() + .await; // Patch the tower address wt_client @@ -1256,7 +1307,7 @@ mod tests { .towers .get_mut(&tower_id) .unwrap() - .set_net_addr(server.base_url()); + .set_net_addr(server.url()); // Check pending data is still there now, and is it not once the retrier succeeds assert_eq!( @@ -1274,24 +1325,19 @@ mod tests { // Send a retry flag to the retrier to force a retry. tx.send((tower_id, RevocationData::None)).unwrap(); + // After retrying the pending pool has been emptied, meaning that both appointments went trough tokio::time::sleep(Duration::from_secs_f64(POLLING_TIME as f64 + MAX_RUN_TIME)).await; - // FIXME: Here we should be able to check this, however, due to httpmock limitations, we cannot return a response based on the request. - // Therefore, both requests will be responded with the same data. Given pending_appointments is a HashSet, we cannot even know which request - // will be sent first (sets are initialized with a random state, which decided the order or iteration). - // https://github.com/alexliesenfeld/httpmock/issues/49 - // assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - // assert!(wt_client - // .lock() - // .unwrap() - // .towers - // .get(&tower_id) - // .unwrap() - // .pending_appointments - // .is_empty()); + assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .is_empty()); + api_mock.assert_async().await; - // This is not much tbh, but looks like its the best we can do at the moment without experiencing random errors. - // Depending on what appointment is sent first the api will be hit either one or two times. - assert!(api_mock.hits() >= 1 && api_mock.hits() <= 2); task.abort(); } @@ -1303,14 +1349,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -1328,18 +1374,19 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Since we are retrying manually, we need to add the data to pending appointments manually too let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; assert_eq!(r, Ok(())); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1350,14 +1397,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // If there are no pending appointments the method will simply return @@ -1373,14 +1420,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -1398,12 +1445,13 @@ mod tests { add_appointment_receipt.sign(&cryptography::get_random_keypair().0); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Since we are retrying manually, we need to add the data to pending appointments manually too let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); @@ -1412,7 +1460,7 @@ mod tests { r, Err(Error::Permanent(RetryError::Misbehaving { .. },)) )); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1454,25 +1502,29 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body( + json!(ApiError { error: "error_msg".to_owned(), error_code: errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR, - })); - }); + }) + .to_string(), + ) + .create_async() + .await; // Add some pending appointments and try again (with an unreachable tower). let appointment = generate_random_appointment(None); @@ -1492,7 +1544,7 @@ mod tests { .. }) )); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1503,25 +1555,29 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body( + json!(ApiError { error: "error_msg".to_owned(), error_code: 1, - })); - }); + }) + .to_string(), + ) + .create_async() + .await; // Add some pending appointments and try again (with an unreachable tower). let appointment = generate_random_appointment(None); @@ -1538,8 +1594,6 @@ mod tests { ); let r = retrier.run().await; - assert_eq!(r, Ok(())); - api_mock.assert(); assert!(wt_client .lock() .unwrap() @@ -1548,6 +1602,8 @@ mod tests { .unwrap() .invalid_appointments .contains(&appointment.locator)); + assert!(r.is_ok()); + api_mock.assert_async().await; } #[tokio::test] @@ -1558,14 +1614,13 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, "http://tower.adrress", &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) From 801ef5d808b3db2b9ad1e01df59c74d95f503200 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 13 Mar 2023 12:54:02 +0100 Subject: [PATCH 085/119] Removes unnecessary `into_iter` from Responder tests The conversion was triggering https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion --- teos/src/responder.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 38efcfc..9ffebda 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -1354,10 +1354,7 @@ mod tests { // Mock data into the GK let target_block_height = START_HEIGHT as u32; let user_id = get_random_user_id(); - let uuids = (0..10) - .into_iter() - .map(|_| generate_uuid()) - .collect::>(); + let uuids = (0..10).map(|_| generate_uuid()).collect::>(); responder .gatekeeper .add_outdated_user(user_id, target_block_height, Some(uuids.clone())); From ca577c558b031acb57c01c1673f4c27d2825d533 Mon Sep 17 00:00:00 2001 From: anipaul2 Date: Sun, 30 Apr 2023 19:05:18 +0530 Subject: [PATCH 086/119] Improve log messages for data directory and configuration details This commit enhances the logging output to provide clearer information about the data directory and configuration file details. By providing this information, it improves the user experience and make it easier for users to find and understand the relevant paths and settings. --- teos/src/cli.rs | 2 +- teos/src/config.rs | 27 ++++++++++++++++++++++++--- teos/src/main.rs | 25 +++++++++++++++++-------- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/teos/src/cli.rs b/teos/src/cli.rs index 5c20f00..eba032b 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -27,7 +27,7 @@ async fn main() { let command = opt.command.clone(); // Load conf (from file or defaults) and patch it with the command line parameters received (if any) - let mut conf = config::from_file::(path.join("teos.toml")); + let mut conf = config::from_file::(&path.join("teos.toml")); conf.patch_with_options(opt); let key = fs::read(&path.join("client-key.pem")) diff --git a/teos/src/config.rs b/teos/src/config.rs index 1669b25..510097c 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -1,6 +1,6 @@ //! Logic related to the tower configuration and command line parameter parsing. -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use structopt::StructOpt; @@ -16,7 +16,7 @@ pub fn data_dir_absolute_path(data_dir: String) -> PathBuf { } } -pub fn from_file(path: PathBuf) -> T { +pub fn from_file(path: &PathBuf) -> T { match std::fs::read(path) { Ok(file_content) => toml::from_slice::(&file_content).map_or_else( |e| { @@ -117,7 +117,7 @@ pub struct Opt { /// - Defaults /// - Configuration file /// - Command line options -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct Config { // API @@ -242,6 +242,27 @@ impl Config { pub fn is_default(&self) -> bool { self == &Config::default() } + + /// Logs non-default options. + pub fn log_non_default_options(&self) { + let json_default_config = serde_json::json!(&Config::default()); + let json_config = serde_json::json!(&self); + let sensitive_args = ["btc_rpc_user", "btc_rpc_password"]; + + for (key, value) in json_config.as_object().unwrap().iter() { + if *value != json_default_config[key] { + log::info!( + "Custom config arg: {}: {}", + key, + if sensitive_args.contains(&key.as_str()) { + "****".to_owned() + } else { + value.to_string() + } + ); + } + } + } } impl Default for Config { diff --git a/teos/src/main.rs b/teos/src/main.rs index 3e61247..a417d7a 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -69,7 +69,7 @@ fn create_new_tower_keypair(db: &DBM) -> (SecretKey, PublicKey) { async fn main() { let opt = Opt::from_args(); let path = config::data_dir_absolute_path(opt.data_dir.clone()); - + let conf_file_path = path.join("teos.toml"); // Create data dir if it does not exist fs::create_dir_all(&path).unwrap_or_else(|e| { eprintln!("Cannot create data dir: {e:?}"); @@ -77,7 +77,7 @@ async fn main() { }); // Load conf (from file or defaults) and patch it with the command line parameters received (if any) - let mut conf = config::from_file::(path.join("teos.toml")); + let mut conf = config::from_file::(&conf_file_path); let is_default = conf.is_default(); conf.patch_with_options(opt); conf.verify().unwrap_or_else(|e| { @@ -103,18 +103,27 @@ async fn main() { .init() .unwrap(); - if is_default { - log::info!("Loading default configuration") - } else { - log::info!("Loading configuration from file") - } - // Create network dir let path_network = path.join(conf.btc_network.clone()); fs::create_dir_all(&path_network).unwrap_or_else(|e| { eprintln!("Cannot create network dir: {e:?}"); std::process::exit(1); }); + + // Log default data dir + log::info!("Default data directory: {:?}", &path); + + // Log datadir path + log::info!("Using data directory: {:?}", &path_network); + + // Log config file path based on whether the config file is found or not + if is_default { + log::info!("Config file: {:?} (not found, skipping)", &conf_file_path); + } else { + log::info!("Config file: {:?}", &conf_file_path); + conf.log_non_default_options(); + } + let dbm = Arc::new(Mutex::new( DBM::new(path_network.join("teos_db.sql3")).unwrap(), )); From 222925170295e0e64b424ca9bd049c563a7a6e0d Mon Sep 17 00:00:00 2001 From: Anmol Agrawal Date: Tue, 7 Mar 2023 08:18:11 +0530 Subject: [PATCH 087/119] Implements ping/pong logic for the tower and CLN plugin Implements ping/pong logic for the tower and CLN plugin Modification in test as suggested and some more Some fixed Signed-off-by: Anmol Agrawal --- teos-common/src/net/http.rs | 2 + teos/src/api/http.rs | 16 ++- watchtower-plugin/README.md | 1 + watchtower-plugin/src/constants.rs | 2 + watchtower-plugin/src/main.rs | 36 +++++- watchtower-plugin/src/net/http.rs | 185 +++++++++++++++++++++++------ 6 files changed, 205 insertions(+), 37 deletions(-) diff --git a/teos-common/src/net/http.rs b/teos-common/src/net/http.rs index 86aa3df..f982bd9 100644 --- a/teos-common/src/net/http.rs +++ b/teos-common/src/net/http.rs @@ -3,6 +3,7 @@ pub enum Endpoint { AddAppointment, GetAppointment, GetSubscriptionInfo, + Ping, } impl std::fmt::Display for Endpoint { @@ -15,6 +16,7 @@ impl std::fmt::Display for Endpoint { Endpoint::AddAppointment => "add_appointment", Endpoint::GetAppointment => "get_appointment", Endpoint::GetSubscriptionInfo => "get_subscription_info", + Endpoint::Ping => "ping", } ) } diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 2655c32..ca9c2c9 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -217,6 +217,14 @@ async fn get_subscription_info( Ok(reply::with_status(body, status)) } +async fn ping(addr: Option) -> Result { + log::debug!( + "Received a ping request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + Ok(reply::reply()) +} + fn router( grpc_conn: PublicTowerServicesClient, ) -> impl Filter + Clone { @@ -251,10 +259,16 @@ fn router( .and(with_grpc(grpc_conn)) .and_then(get_subscription_info); + let ping = warp::get() + .and(warp::path(Endpoint::Ping.to_string())) + .and(warp::addr::remote()) + .and_then(ping); + register .or(add_appointment) .or(get_appointment) .or(get_subscription_info) + .or(ping) .recover(handle_rejection) } @@ -615,7 +629,7 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); } #[tokio::test] diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index 1810653..0f9ec7e 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -12,6 +12,7 @@ The plugin has the following methods: - `gettowerinfo `: gets all the locally stored data about a given tower. - `retrytower `: tries to send pending appointment to a (previously) unreachable tower. - `abandontower `: deletes all data associated with a given tower. +- `pingtower `: Polls the tower to check if it is online. - `listtowers`: lists all registered towers. - `getappointment `: queries a given tower about an appointment. - `getsubscriptioninfo `: gets the subscription information by querying the tower. diff --git a/watchtower-plugin/src/constants.rs b/watchtower-plugin/src/constants.rs index 1b14803..90c50d8 100644 --- a/watchtower-plugin/src/constants.rs +++ b/watchtower-plugin/src/constants.rs @@ -44,6 +44,8 @@ pub const RPC_RETRY_TOWER_DESC: &str = "Retries to send pending appointment to an unreachable tower"; pub const RPC_ABANDON_TOWER: &str = "abandontower"; pub const RPC_ABANDON_TOWER_DESC: &str = "Forgets about a tower and wipes all local data"; +pub const RPC_PING: &str = "pingtower"; +pub const RPC_PING_DESC: &str = "Polls the tower to check if it is online"; /// Collections of hook names diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 3d4910c..5778e8d 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -20,7 +20,8 @@ use teos_common::{cryptography, errors}; use watchtower_plugin::convert::{CommitmentRevocation, GetAppointmentParams, RegisterParams}; use watchtower_plugin::net::http::{ - self, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, + self, get_request, post_request, process_post_response, AddAppointmentError, ApiResponse, + RequestError, }; use watchtower_plugin::net::ProxyInfo; use watchtower_plugin::retrier::RetryManager; @@ -295,6 +296,38 @@ async fn get_tower_info( } } +async fn ping( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let (tower_net_addr, proxy) = { + // Check if the tower_id is known to the plugin + let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; + let state = plugin.state().lock().unwrap(); + ( + state + .towers + .get(&tower_id) + .ok_or(anyhow!("Unknown tower_id"))? + .net_addr + .clone(), + state.proxy.clone(), + ) + }; + let response = get_request(&tower_net_addr, Endpoint::Ping, &proxy) + .await + .map_err(to_cln_error)?; + + if response.status().is_success() { + Ok(json!("Tower is reachable")) + } else { + Err(anyhow!(format!( + "Tower cannot be reached (Error: {})", + response.status() + ))) + } +} + /// Triggers a manual retry of a tower, tries to send all pending appointments to it. /// /// Only works if the tower is unreachable or there's been a subscription error (and the tower is not already being retried). @@ -556,6 +589,7 @@ async fn main() -> Result<(), Error> { constants::RPC_GET_TOWER_INFO_DESC, get_tower_info, ) + .rpcmethod(constants::RPC_PING, constants::RPC_PING_DESC, ping) .rpcmethod( constants::RPC_RETRY_TOWER, constants::RPC_RETRY_TOWER_DESC, diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index b789b02..1a93633 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -1,4 +1,4 @@ -use reqwest::Response; +use reqwest::{Method, Response}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use teos_common::appointment::Appointment; @@ -154,12 +154,13 @@ pub async fn send_appointment( } } -/// Generic function to post different types of requests to the tower. -pub async fn post_request( +/// A generic function to send a request to a tower. +async fn request( tower_net_addr: &NetAddr, endpoint: Endpoint, - data: S, proxy: &Option, + method: Method, + data: Option, ) -> Result { let client = if let Some(proxy) = proxy { if proxy.always_use || tower_net_addr.is_onion() { @@ -183,23 +184,42 @@ pub async fn post_request( reqwest::Client::new() }; - client - .post(format!("{}{}", tower_net_addr.net_addr(), endpoint.path())) - .json(&data) - .send() - .await - .map_err(|e| { - log::debug!("An error ocurred when sending data to the tower: {e}"); - if e.is_connect() | e.is_timeout() { - RequestError::ConnectionError( - "Cannot connect to the tower. Connection refused".to_owned(), - ) - } else { - RequestError::Unexpected( - "Unexpected error ocurred (see logs for more info)".to_owned(), - ) - } - }) + let mut request_builder = client.request( + method, + format!("{}{}", tower_net_addr.net_addr(), endpoint.path()), + ); + + if let Some(data) = data { + request_builder = request_builder.json(&data); + } + + request_builder.send().await.map_err(|e| { + log::debug!("An error ocurred when sending data to the tower: {e}"); + if e.is_connect() | e.is_timeout() { + RequestError::ConnectionError( + "Cannot connect to the tower. Connection refused".to_owned(), + ) + } else { + RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".to_owned()) + } + }) +} + +pub async fn post_request( + tower_net_addr: &NetAddr, + endpoint: Endpoint, + data: S, + proxy: &Option, +) -> Result { + request(tower_net_addr, endpoint, proxy, Method::POST, Some(data)).await +} + +pub async fn get_request( + tower_net_addr: &NetAddr, + endpoint: Endpoint, + proxy: &Option, +) -> Result { + request::<()>(tower_net_addr, endpoint, proxy, Method::GET, None).await } /// Generic function to process the response of a given post request. @@ -502,6 +522,104 @@ mod tests { assert!(matches!(error, AddAppointmentError::ApiError { .. })); } + #[tokio::test] + async fn test_request() { + let mut server = mockito::Server::new_async().await; + + // Test with POST + let api_mock_post = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + + let response_post = request( + &NetAddr::new(server.url()), + Endpoint::Register, + &None, + Method::POST, + Some(json!("")), + ) + .await; + + api_mock_post.assert_async().await; + assert!(matches!(response_post, Ok(Response { .. }))); + + // Test with GET + let api_mock_get = server + .mock("GET", Endpoint::Ping.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + + let response_get = request::<()>( + &NetAddr::new(server.url()), + Endpoint::Ping, + &None, + Method::GET, + None, + ) + .await; + + api_mock_get.assert_async().await; + assert!(matches!(response_get, Ok(Response { .. }))); + } + + #[tokio::test] + async fn test_request_connection_error() { + assert!(request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Register, + &None, + Method::POST, + Some(json!("")), + ) + .await + .unwrap_err() + .is_connection()); + + assert!(request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Ping, + &None, + Method::GET, + None::<&str>, + ) + .await + .unwrap_err() + .is_connection()); + } + + #[tokio::test] + async fn test_get_request() { + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("GET", Endpoint::Ping.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + let response = get_request(&NetAddr::new(server.url()), Endpoint::Ping, &None).await; + + api_mock.assert_async().await; + + assert!(matches!(response, Ok(Response { .. }))); + } + + #[tokio::test] + async fn test_get_request_connection_error() { + assert!(get_request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Ping, + &None, + ) + .await + .unwrap_err() + .is_connection()); + } + #[tokio::test] async fn test_post_request() { let mut server = mockito::Server::new_async().await; @@ -518,26 +636,23 @@ mod tests { json!(""), &None, ) - .await - .unwrap(); + .await; api_mock.assert_async().await; - assert!(matches!(response, Response { .. })); + assert!(matches!(response, Ok(Response { .. }))); } #[tokio::test] async fn test_post_request_connection_error() { - assert!(matches!( - post_request( - &NetAddr::new("http://unreachable_url".to_owned()), - Endpoint::Register, - json!(""), - &None, - ) - .await - .unwrap_err(), - RequestError::ConnectionError { .. } - )); + assert!(post_request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Register, + json!(""), + &None, + ) + .await + .unwrap_err() + .is_connection()); } #[tokio::test] From c0f4ebc82cdb68e858ccfd501ec483d433a13334 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 3 Apr 2023 13:14:40 +0100 Subject: [PATCH 088/119] Makes teos-cli cannot connect to backend error message more friendly Let's not just spit a debug error when calling `teos-cli` pointing to an unreachable backend. --- teos/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/teos/src/cli.rs b/teos/src/cli.rs index eba032b..dbc345d 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -56,8 +56,8 @@ async fn main() { }) .connect() .await - .unwrap_or_else(|e| { - eprintln!("Could not connect to tower: {e:?}"); + .unwrap_or_else(|_| { + eprintln!("Could not connect to tower. Is teosd running?"); std::process::exit(1); }); From 7fc30577ac7abe0a922943e805fe5147d61aa8d8 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 30 Mar 2023 10:46:54 +0100 Subject: [PATCH 089/119] Adds some notes on commits and GitHub to CONTRIBUTING --- CONTRIBUTING.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90fd989..b646bcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,35 @@ pub struct Responder { ## Test Coverage Tests should be provided to cover both positive and negative conditions. Tests should cover both the proper execution as well as all the covered error paths. PR with no proper test coverage will not be merged. -## Signing Commits +## Git conventions + +### Commits, titles, and descriptions + +- Changes must be split logically in commits, such that a commit is self-contained +- In general terms, all commits need to pass the test suite. There may be some exceptions to this rule if the change you are working on touches several components of the codebase and it makes more sense to split the change by component (or group of components) +- Commit titles need to be short and explanatory. If we are, for instance, adding an RPC command to the backend, "Adds command X to the backend" will be a good short description, "Add command" or "Fix #123" where #123 is an issue referencing this feature **IS NOT** +- Descriptions can be provided to give more context about what has been fixed and how + +### Pull requests + +- Pull request titles need to be explanatory, in the same way, commits titles were. If a PR includes a single commit, they can share the title, otherwise, a general title of what we are trying to achieve is required. **DO NOT REFERENCE ISSUES IN PULL REQUEST TITLES**, save that for the PR description +- PR descriptions need to guide the reviewer into what has been changed. You can reference issues here. If the PR is a fix of a simple issue, "Fix #123" may suffice, however, if it involves several changes, a proper explanation of both what has been fixed and how is due. These are two good examples of PR descriptions, both long and short: [188](https://github.com/talaia-labs/rust-teos/pull/188), [194](https://github.com/talaia-labs/rust-teos/pull/194) +- **WE DO NOT PILE "fix" COMMITS IN A PULL REQUEST**, that is, if some fixes are requested by reviewers, or something was missing from our original approach, it needs to be squashed. Do **NOT** do this: + + ``` + 886b0ff Adds X functionality to component Y + 801ff5d Fixes the previous commit because Z + 67ac345 Addresses review comments + 7dc7fcd Updates X because G was missing + b60999c Adds missing test + ... + ``` + +- Create a new branch to work on your pull request. **DO NOT** work from the master branch of your fork* +- **DO NOT** merge master into your branch, rebase master instead* + + \* If you're not sure how to handle this, check external documentation on how to manage multiple remotes for the same repository. + +### Signing Commits We require that all commits to be merged into master are signed. You can enable commit signing on GitHub by following [Signing commits](https://help.github.com/en/github/authenticating-to-github/signing-commits). From 2bf555c07fdea6458263e13d9b0185edeccb72d8 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 1 May 2023 14:44:59 -0400 Subject: [PATCH 090/119] Adds --forceupdate to teosd If a tower hasn't been running for a long time and the backend runs in pruned mode it could be the case that by the time the tower comes back online, the Last known block by the tower is not being known by the node anymore. In this situation, the tower cannot bootstrap normally, given the cache cannot be populated. This commits adds a new argument to teosd (`--forceupdate`) that can be used to force a tower to update its last known block to the earliest known block by the backend under this situation. Notice that doing so may make the tower miss some of its state transitions (the ones triggered by missed blocks), so this must be done as a last resource. --- teos/src/config.rs | 9 ++++++ teos/src/main.rs | 75 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/teos/src/config.rs b/teos/src/config.rs index 510097c..5436725 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -102,6 +102,11 @@ pub struct Opt { #[structopt(long)] pub tor_support: bool, + /// Forces the tower to run even if the underlying chain has gone too far out of sync. This can only happen + /// if the node is being run in pruned mode. + #[structopt(long)] + pub force_update: bool, + /// Tor control port [default: 9051] #[structopt(long)] pub tor_control_port: Option, @@ -139,6 +144,7 @@ pub struct Config { pub debug: bool, pub deps_debug: bool, pub overwrite_key: bool, + pub force_update: bool, // General pub subscription_slots: u32, @@ -198,6 +204,7 @@ impl Config { self.debug |= options.debug; self.deps_debug |= options.deps_debug; self.overwrite_key = options.overwrite_key; + self.force_update = options.force_update; } /// Verifies that [Config] is properly built. @@ -290,6 +297,7 @@ impl Default for Config { debug: false, deps_debug: false, overwrite_key: false, + force_update: false, subscription_slots: 10000, subscription_duration: 4320, expiry_delta: 6, @@ -325,6 +333,7 @@ mod tests { debug: false, deps_debug: false, overwrite_key: false, + force_update: false, } } } diff --git a/teos/src/main.rs b/teos/src/main.rs index a417d7a..ed3e8b1 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -11,12 +11,12 @@ use tonic::transport::{Certificate, Server, ServerTlsConfig}; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use bitcoincore_rpc::{Auth, Client}; +use bitcoincore_rpc::{Auth, Client, RpcApi}; use lightning_block_sync::init::validate_best_block_header; use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, }; -use lightning_block_sync::{BlockSource, SpvClient, UnboundedCache}; +use lightning_block_sync::{BlockSource, BlockSourceError, SpvClient, UnboundedCache}; use teos::api::internal::InternalAPI; use teos::api::{http, tor::TorAPI}; @@ -41,22 +41,19 @@ async fn get_last_n_blocks( poller: &mut ChainPoller, mut last_known_block: ValidatedBlockHeader, n: usize, -) -> Vec +) -> Result, BlockSourceError> where B: DerefMut + Sized + Send + Sync, T: BlockSource, { let mut last_n_blocks = Vec::with_capacity(n); for _ in 0..n { - let block = poller.fetch_block(&last_known_block).await.unwrap(); - last_known_block = poller - .look_up_previous_header(&last_known_block) - .await - .unwrap(); + let block = poller.fetch_block(&last_known_block).await?; + last_known_block = poller.look_up_previous_header(&last_known_block).await?; last_n_blocks.push(block); } - last_n_blocks + Ok(last_n_blocks) } fn create_new_tower_keypair(db: &DBM) -> (SecretKey, PublicKey) { @@ -186,14 +183,53 @@ async fn main() { // Load last known block from DB if found. Poll it from Bitcoind otherwise. let last_known_block = dbm.lock().unwrap().load_last_known_block(); let tip = if let Some(block_hash) = last_known_block { - derefed + let mut last_known_header = derefed .get_header(&block_hash, None) .await .unwrap() .validate(block_hash) - .unwrap() + .unwrap(); + + log::info!( + "Last known block: {} (height: {})", + last_known_header.header.block_hash(), + last_known_header.height + ); + + // If we are running in pruned mode some data may be missing (if we happen to have been offline for a while) + if let Some(prune_height) = rpc.get_blockchain_info().unwrap().prune_height { + if last_known_header.height - IRREVOCABLY_RESOLVED + 1 < prune_height as u32 { + log::warn!( + "Cannot load blocks in the range {}-{}. Chain has gone too far out of sync", + last_known_header.height - IRREVOCABLY_RESOLVED + 1, + last_known_header.height + ); + if conf.force_update { + log::info!("Forcing a backend update"); + // We want to grab the first IRREVOCABLY_RESOLVED we know about for the initial cache + // So we can perform transitions from there onwards. + let target_height = prune_height + IRREVOCABLY_RESOLVED as u64; + let target_hash = rpc.get_block_hash(target_height).unwrap(); + last_known_header = derefed + .get_header( + &rpc.get_block_hash(target_height).unwrap(), + Some(target_height as u32), + ) + .await + .unwrap() + .validate(target_hash) + .unwrap(); + } else { + log::error!( + "The underlying chain has gone too far out of sync. The tower block cache cannot be initialized. Run with --forceupdate to force update. THIS WILL, POTENTIALLY, MAKE THE TOWER MISS SOME OF ITS APPOINTMENTS" + ); + std::process::exit(1); + } + } + } + last_known_header } else { - validate_best_block_header(&mut derefed).await.unwrap() + validate_best_block_header(&derefed).await.unwrap() }; // DISCUSS: This is not really required (and only triggered in regtest). This is only in place so the caches can be @@ -208,7 +244,11 @@ async fn main() { std::process::exit(1); } - log::info!("Last known block: {}", tip.header.block_hash()); + log::info!( + "Current chain tip: {} (height: {})", + tip.header.block_hash(), + tip.height + ); // This is how chain poller names bitcoin networks. let btc_network = match conf.btc_network.as_str() { @@ -218,7 +258,14 @@ async fn main() { }; let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); - let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize).await; + let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize) + .await.unwrap_or_else(|e| { + // I'm pretty sure this can only happen if we are pulling blocks from the target to the prune height, and by the time we get to + // the end at least one has been pruned. + log::error!("Couldn't load the latest {IRREVOCABLY_RESOLVED} blocks. Please try again (Error: {})", e.into_inner()); + std::process::exit(1); + } + ); // Build components let gatekeeper = Arc::new(Gatekeeper::new( From 7093882f47575b1768fddb7452bfc269b89f2991 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 26 May 2023 15:50:33 -0400 Subject: [PATCH 091/119] Properly patches sendrawtransaction so it does not crash Currently, when mocking sendrawtransaction so it does nothing, the client will raise an exception given the picked lambda does not accept any params (and sendrawtransaction has some). Patches it so this does not happen. This patch is pretty minimal, given the behavior wrt CLN does not change, but it feels better not to except here. --- watchtower-plugin/tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 19d633d..2066d1b 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -48,7 +48,7 @@ def test_watchtower(node_factory, bitcoind, teosd): locator = change_endianness(dispute_txid[32:]) # Make sure l2's normal penalty_tx doesn't reach the network - l2.daemon.rpcproxy.mock_rpc("sendrawtransaction", lambda: None) + l2.daemon.rpcproxy.mock_rpc("sendrawtransaction", lambda _: {"result": None, "error": None, "id": "pytest"}) l2.start() # The tower will react once the dispute gets confirmed. For now it is still watching for it From ed78c896132411e85cc233a85ab2d228c389b802 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 18 Jul 2023 15:57:53 -0400 Subject: [PATCH 092/119] Fixes clippy issues, updates warp Fixes some clippy issues with rustc 1.71.0 and updates warp to fix some address some warnings regarding `buf_redux v0.8.4` and `multipart v0.18.0` --- Cargo.lock | 160 +++++++++++++++++++---------------------- teos/Cargo.toml | 2 +- teos/src/gatekeeper.rs | 2 +- 3 files changed, 76 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9302093..89eb349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + [[package]] name = "base64-compat" version = "1.0.0" @@ -232,16 +238,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" version = "3.10.0" @@ -515,9 +511,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -872,14 +868,14 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ - "base64", + "base64 0.13.0", "bitflags", "bytes 1.1.0", "headers-core", "http", "httpdate 1.0.2", "mime", - "sha-1 0.10.0", + "sha-1", ] [[package]] @@ -1428,30 +1424,30 @@ dependencies = [ "tokio 1.25.0", ] +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes 1.1.0", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "native-tls" version = "0.2.10" @@ -1660,7 +1656,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" dependencies = [ - "base64", + "base64 0.13.0", ] [[package]] @@ -1849,12 +1845,6 @@ dependencies = [ "prost 0.9.0", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.15" @@ -2026,7 +2016,7 @@ version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ - "base64", + "base64 0.13.0", "bytes 1.1.0", "encoding_rs", "futures-core", @@ -2073,7 +2063,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi 0.3.9", @@ -2132,25 +2122,28 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.0", "log", "ring", "sct", "webpki", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.2", +] + [[package]] name = "ryu" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "schannel" version = "0.1.20" @@ -2275,19 +2268,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -2296,7 +2276,18 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", ] [[package]] @@ -2397,6 +2388,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.8.0" @@ -2706,13 +2703,12 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "pin-project", "tokio 1.25.0", "tungstenite", ] @@ -2776,7 +2772,7 @@ checksum = "796c5e1cd49905e65dd8e700d4cb1dffcbfdb4fc9d017de08c1a537afd83627c" dependencies = [ "async-stream", "async-trait", - "base64", + "base64 0.13.0", "bytes 1.1.0", "futures-core", "futures-util", @@ -2808,7 +2804,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64", + "base64 0.13.0", "bytes 1.1.0", "futures-core", "futures-util", @@ -2851,7 +2847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69" dependencies = [ "base32", - "base64", + "base64 0.13.0", "derive_more", "ed25519-dalek", "hex", @@ -2953,32 +2949,23 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ - "base64", + "base64 0.13.0", "byteorder", "bytes 1.1.0", "http", "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha1", "thiserror", "url", "utf-8", ] -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - [[package]] name = "typenum" version = "1.15.0" @@ -3097,9 +3084,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" dependencies = [ "bytes 1.1.0", "futures-channel", @@ -3110,9 +3097,10 @@ dependencies = [ "log", "mime", "mime_guess", - "multipart", + "multer", "percent-encoding", "pin-project", + "rustls-pemfile", "scoped-tls", "serde", "serde_json", @@ -3120,7 +3108,7 @@ dependencies = [ "tokio 1.25.0", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.6.9", + "tokio-util 0.7.0", "tower-service", "tracing", ] @@ -3429,7 +3417,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" dependencies = [ - "base64", + "base64 0.13.0", "chrono", "data-encoding", "der-parser", diff --git a/teos/Cargo.toml b/teos/Cargo.toml index b062b46..f01ab5c 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -30,7 +30,7 @@ toml = "0.5" tonic = { version = "0.6", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread" ] } triggered = "0.1.2" -warp = "0.3.2" +warp = "0.3.5" torut = "0.2.1" # Bitcoin and Lightning diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index d306453..3106991 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -388,7 +388,7 @@ mod tests { ) { self.add_update_user(user_id).unwrap(); let mut registered_users = self.registered_users.lock().unwrap(); - let mut user = registered_users.get_mut(&user_id).unwrap(); + let user = registered_users.get_mut(&user_id).unwrap(); user.subscription_expiry = outdates_at - self.expiry_delta; if let Some(uuids) = appointments { for uuid in uuids.iter() { From 565eda42ba1e60ff7e05621690e1657ba1679a97 Mon Sep 17 00:00:00 2001 From: optimm Date: Tue, 4 Jul 2023 03:39:20 +0530 Subject: [PATCH 093/119] printing the cli errors to standard error stream Added a function that prints the error to the standard error stream and exits the process with a status code of 1, this is to seprate the cli errors from regular output --- teos/src/cli.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/teos/src/cli.rs b/teos/src/cli.rs index dbc345d..3ef1d9f 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -13,6 +13,12 @@ use teos::protos::private_tower_services_client::PrivateTowerServicesClient; use teos_common::appointment::Locator; use teos_common::UserId; +/// Prints the cli error to standard error and exits the process +fn handle_error(error: T) { + eprintln!("{}", error); + std::process::exit(1); +} + #[tokio::main] async fn main() { let opt = Opt::from_args(); @@ -80,10 +86,10 @@ async fn main() { Ok(appointments) => { println!("{}", pretty_json(&appointments.into_inner()).unwrap()) } - Err(status) => println!("{}", status.message()), + Err(status) => handle_error(status.message()), } } - Err(e) => println!("{e}"), + Err(e) => handle_error(e), }; } Command::GetTowerInfo => { @@ -106,10 +112,10 @@ async fn main() { Ok(response) => { println!("{}", pretty_json(&response.into_inner()).unwrap()) } - Err(status) => println!("{}", status.message()), + Err(status) => handle_error(status.message()), } } - Err(e) => println!("{e}"), + Err(e) => handle_error(e), }; } Command::Stop => { From 092f549f7e150a161699fb41b315fad1b2bfd6d2 Mon Sep 17 00:00:00 2001 From: anipaul2 Date: Tue, 25 Jul 2023 16:46:32 +0530 Subject: [PATCH 094/119] Use black for `.py` files formatting --- .github/workflows/build.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b1b91fe..5a90464 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,4 +46,15 @@ jobs: cargo fmt --verbose --check -- --color always - name: Run clippy run: | - cargo clippy --all-features --all-targets --color always -- --deny warnings \ No newline at end of file + cargo clippy --all-features --all-targets --color always -- --deny warnings + + python-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v3 + - name: Run black + uses: psf/black@stable + with: + src: "./watchtower-plugin/tests" + options: "--check -l 120" \ No newline at end of file From 8df58f99d1f8d32ef4bcbb35193173fb4be947b1 Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Fri, 17 Feb 2023 23:50:47 +0200 Subject: [PATCH 095/119] loading minimal data during bootstrap By loading the minimal necessary data during bootstrap, we get lower memory usage and faster bootstrapping. Co-authored-by: Sergi Delgado Segura --- teos/src/dbm.rs | 65 +++++++++++++++++++++++++++++++- teos/src/extended_appointment.rs | 6 +++ teos/src/responder.rs | 19 +++++++--- teos/src/watcher.rs | 9 ++--- 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 4a53abd..16c3f39 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -19,9 +19,9 @@ use teos_common::constants::ENCRYPTED_BLOB_MAX_SIZE; use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; use teos_common::UserId; -use crate::extended_appointment::{ExtendedAppointment, UUID}; +use crate::extended_appointment::{AppointmentSummary, ExtendedAppointment, UUID}; use crate::gatekeeper::UserInfo; -use crate::responder::{ConfirmationStatus, TransactionTracker}; +use crate::responder::{ConfirmationStatus, TrackerSummary, TransactionTracker}; const TABLES: [&str; 5] = [ "CREATE TABLE IF NOT EXISTS users ( @@ -305,6 +305,34 @@ impl DBM { .ok() } + /// Loads all [AppointmentSummary]s from that database. + pub(crate) fn load_appointment_summaries(&self) -> HashMap { + let mut summaries = HashMap::new(); + + let mut stmt = self + .connection + .prepare( + "SELECT a.UUID, a.locator, a.user_id + FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL", + ) + .unwrap(); + let mut rows = stmt.query([]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let raw_uuid: Vec = row.get(0).unwrap(); + let raw_locator: Vec = row.get(1).unwrap(); + let raw_userid: Vec = row.get(2).unwrap(); + summaries.insert( + UUID::from_slice(&raw_uuid).unwrap(), + AppointmentSummary::new( + Locator::from_slice(&raw_locator).unwrap(), + UserId::from_slice(&raw_userid).unwrap(), + ), + ); + } + summaries + } + /// Loads appointments from the database. If a locator is given, this method loads only the appointments /// matching this locator. If no locator is given, all the appointments in the database would be returned. pub(crate) fn load_appointments( @@ -481,6 +509,39 @@ impl DBM { .ok() } + /// Loads all [TrackerSummary]s from that database. + pub(crate) fn load_tracker_summaries(&self) -> HashMap { + let mut summaries = HashMap::new(); + + let mut stmt = self + .connection + .prepare( + "SELECT t.UUID, t.penalty_tx, t.height, t.confirmed, a.user_id + FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID", + ) + .unwrap(); + let mut rows = stmt.query([]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let raw_uuid: Vec = row.get(0).unwrap(); + let raw_penalty_tx: Vec = row.get(1).unwrap(); + let height: u32 = row.get(2).unwrap(); + let confirmed: bool = row.get(3).unwrap(); + let raw_userid: Vec = row.get(4).unwrap(); + summaries.insert( + UUID::from_slice(&raw_uuid).unwrap(), + TrackerSummary::new( + UserId::from_slice(&raw_userid).unwrap(), + consensus::deserialize::(&raw_penalty_tx) + .unwrap() + .txid(), + ConfirmationStatus::from_db_data(height, confirmed), + ), + ); + } + summaries + } + /// Loads trackers from the database. If a locator is given, this method loads only the trackers /// matching this locator. If no locator is given, all the trackers in the database would be returned. pub(crate) fn load_trackers( diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index 88920de..32cc0b9 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -72,6 +72,12 @@ pub(crate) struct AppointmentSummary { pub user_id: UserId, } +impl AppointmentSummary { + pub fn new(locator: Locator, user_id: UserId) -> Self { + Self { locator, user_id } + } +} + impl ExtendedAppointment { /// Create a new [ExtendedAppointment]. pub fn new( diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 9ffebda..3ace73e 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -83,6 +83,16 @@ pub(crate) struct TrackerSummary { status: ConfirmationStatus, } +impl TrackerSummary { + pub fn new(user_id: UserId, penalty_txid: Txid, status: ConfirmationStatus) -> Self { + Self { + user_id, + penalty_txid, + status, + } + } +} + /// Structure to keep track of triggered appointments. /// /// It is analogous to [ExtendedAppointment](crate::extended_appointment::ExtendedAppointment) for the [`Watcher`](crate::watcher::Watcher). @@ -163,14 +173,13 @@ impl Responder { let mut trackers = HashMap::new(); let mut tx_tracker_map: HashMap> = HashMap::new(); - for (uuid, tracker) in dbm.lock().unwrap().load_trackers(None) { - trackers.insert(uuid, tracker.get_summary()); - - if let Some(map) = tx_tracker_map.get_mut(&tracker.penalty_tx.txid()) { + for (uuid, summary) in dbm.lock().unwrap().load_tracker_summaries() { + if let Some(map) = tx_tracker_map.get_mut(&summary.penalty_txid) { map.insert(uuid); } else { - tx_tracker_map.insert(tracker.penalty_tx.txid(), HashSet::from_iter(vec![uuid])); + tx_tracker_map.insert(summary.penalty_txid, HashSet::from_iter(vec![uuid])); } + trackers.insert(uuid, summary); } Responder { diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index ec2e7da..8a6f058 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -141,14 +141,13 @@ impl Watcher { ) -> Self { let mut appointments = HashMap::new(); let mut locator_uuid_map: HashMap> = HashMap::new(); - for (uuid, appointment) in dbm.lock().unwrap().load_appointments(None) { - appointments.insert(uuid, appointment.get_summary()); - - if let Some(map) = locator_uuid_map.get_mut(&appointment.locator()) { + for (uuid, summary) in dbm.lock().unwrap().load_appointment_summaries() { + if let Some(map) = locator_uuid_map.get_mut(&summary.locator) { map.insert(uuid); } else { - locator_uuid_map.insert(appointment.locator(), HashSet::from_iter(vec![uuid])); + locator_uuid_map.insert(summary.locator, HashSet::from_iter(vec![uuid])); } + appointments.insert(uuid, summary); } Watcher { From fad3ad1c08d87520d551aca06a19f7bdcf187a3d Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Wed, 22 Feb 2023 12:11:45 +0200 Subject: [PATCH 096/119] dropping `last_known_blocks` after using it `last_known_blocks` was taking up ~300migs of memory (for 100 blocks) because it was not dropped in `main`. Co-authored-by: Sergi Delgado Segura --- teos/src/main.rs | 56 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/teos/src/main.rs b/teos/src/main.rs index ed3e8b1..1c60eed 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -257,16 +257,6 @@ async fn main() { any => any, }; - let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); - let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize) - .await.unwrap_or_else(|e| { - // I'm pretty sure this can only happen if we are pulling blocks from the target to the prune height, and by the time we get to - // the end at least one has been pruned. - log::error!("Couldn't load the latest {IRREVOCABLY_RESOLVED} blocks. Please try again (Error: {})", e.into_inner()); - std::process::exit(1); - } - ); - // Build components let gatekeeper = Arc::new(Gatekeeper::new( tip.height, @@ -276,23 +266,35 @@ async fn main() { dbm.clone(), )); - let carrier = Carrier::new(rpc, bitcoind_reachable.clone(), tip.height); - let responder = Arc::new(Responder::new( - &last_n_blocks, - tip.height, - carrier, - gatekeeper.clone(), - dbm.clone(), - )); - let watcher = Arc::new(Watcher::new( - gatekeeper.clone(), - responder.clone(), - &last_n_blocks[0..6], - tip.height, - tower_sk, - TowerId(tower_pk), - dbm.clone(), - )); + let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); + let (responder, watcher) = { + let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize) + .await.unwrap_or_else(|e| { + // I'm pretty sure this can only happen if we are pulling blocks from the target to the prune height, and by the time we get to + // the end at least one has been pruned. + log::error!("Couldn't load the latest {IRREVOCABLY_RESOLVED} blocks. Please try again (Error: {})", e.into_inner()); + std::process::exit(1); + } + ); + + let responder = Arc::new(Responder::new( + &last_n_blocks, + tip.height, + Carrier::new(rpc, bitcoind_reachable.clone(), tip.height), + gatekeeper.clone(), + dbm.clone(), + )); + let watcher = Arc::new(Watcher::new( + gatekeeper.clone(), + responder.clone(), + &last_n_blocks[0..6], + tip.height, + tower_sk, + TowerId(tower_pk), + dbm.clone(), + )); + (responder, watcher) + }; if watcher.is_fresh() & responder.is_fresh() & gatekeeper.is_fresh() { log::info!("Fresh bootstrap"); From cab6151ccc632a481896d4862771ed884502ff20 Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Tue, 7 Mar 2023 18:50:54 +0200 Subject: [PATCH 097/119] Getting rid of in-memory data to avoid unbounded memory growth Regrading the `Watcher`, fields (appointments, locator_uuid_map) has been replaced by DB calls when needed. For `Responder`, the field `trackers` has been replaced by DB calls when needed, and `tx_tracker_map` wasn't actually needed for the tower to operate, so was just dropped. For `GateKeeper`, `registered_users::appointments` which used to hold the uuids of every appointment the user submitted was removed so that `registered_users` only holds meta information about users. Also now the gatekeeper is the entity responsible for deleting appointments from the database. Instead of the watcher/responder asking the gatekeeper for the users to update and carry out the deletion and update itself, now the watcher/responder will hand the gatekeeper the uuids to delete and the gatekeeper will figure out which users it needs to update (refund the freed slots to). Also now, like in `Watcher::store_triggered_appointment`, if the appointment is invalid or was rejected by the network in block connections, the freed slots will not be refunded to the user. Also the block connection order starts with the gatekeeper first, this allows the gatekeeper to delete the outdated users so that the watcher and the responder doesn't take them into account. --- teos-common/src/test_utils.rs | 6 + teos/src/api/http.rs | 21 +- teos/src/api/internal.rs | 77 +- teos/src/dbm.rs | 772 ++++++++++--- teos/src/extended_appointment.rs | 50 +- teos/src/gatekeeper.rs | 489 ++++---- teos/src/main.rs | 5 +- teos/src/responder.rs | 1831 ++++++++++-------------------- teos/src/test_utils.rs | 14 +- teos/src/tx_index.rs | 25 +- teos/src/watcher.rs | 1167 ++++++------------- 11 files changed, 1947 insertions(+), 2510 deletions(-) diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 9eaed7a..dd0a333 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -32,6 +32,12 @@ pub fn get_random_user_id() -> UserId { UserId(pk) } +pub fn get_random_locator() -> Locator { + let mut rng = rand::thread_rng(); + + Locator::from_slice(&rng.gen::<[u8; 16]>()).unwrap() +} + pub fn generate_random_appointment(dispute_txid: Option<&Txid>) -> Appointment { let dispute_txid = match dispute_txid { Some(l) => *l, diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index ca9c2c9..a041ee3 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -660,8 +660,11 @@ mod tests_methods { }; use super::*; - use crate::extended_appointment::UUID; - use crate::test_utils::{generate_dummy_appointment, ApiConfig, DURATION, SLOTS}; + use crate::responder::{ConfirmationStatus, TransactionTracker}; + use crate::test_utils::{ + generate_dummy_appointment, get_random_tx, ApiConfig, DURATION, SLOTS, + }; + use crate::watcher::Breach; use teos_common::test_utils::get_random_user_id; use teos_common::{cryptography, UserId}; @@ -830,14 +833,20 @@ mod tests_methods { .await .unwrap(); - // Add the appointment to the Responder so it counts as triggered - let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + // Add the appointment to the Responder as a tracker so it counts as triggered + let dispute_tx = get_random_tx(); + let tracker = TransactionTracker::new( + Breach::new(dispute_tx.clone(), get_random_tx()), + UserId(user_pk), + ConfirmationStatus::ConfirmedIn(100), + ); internal_api .get_watcher() - .add_random_tracker_to_responder(UUID::new(appointment.locator, UserId(user_pk))); + .add_dummy_tracker_to_responder(&tracker); // Try to add it via the http API + let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); assert_eq!( check_api_error( Endpoint::AddAppointment, diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index c6fa355..fc2085d 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, Condvar, Mutex}; use tonic::{Code, Request, Response, Status}; use triggered::Trigger; +use crate::extended_appointment::UUID; use crate::protos as msgs; use crate::protos::private_tower_services_server::PrivateTowerServices; use crate::protos::public_tower_services_server::PublicTowerServices; @@ -386,10 +387,14 @@ impl PrivateTowerServices for Arc { })?; match self.watcher.get_user_info(user_id) { - Some(info) => Ok(Response::new(msgs::GetUserResponse { + Some((info, locators)) => Ok(Response::new(msgs::GetUserResponse { available_slots: info.available_slots, subscription_expiry: info.subscription_expiry, - appointments: info.appointments.keys().map(|uuid| uuid.to_vec()).collect(), + // TODO: Should make it return locators and make `get_appointments` queryable using the (user_id, locator) pair for consistency. + appointments: locators + .into_iter() + .map(|locator| UUID::new(locator, user_id).to_vec()) + .collect(), })), None => Err(Status::new(Code::NotFound, "User not found")), } @@ -429,11 +434,10 @@ mod tests_private_api { use bitcoin::hashes::Hash; use bitcoin::Txid; - use crate::extended_appointment::UUID; use crate::responder::{ConfirmationStatus, TransactionTracker}; use crate::test_utils::{ - create_api, generate_dummy_appointment, generate_uuid, get_random_tx, DURATION, SLOTS, - START_HEIGHT, + create_api, generate_dummy_appointment, generate_dummy_appointment_with_user, + get_random_tx, DURATION, SLOTS, START_HEIGHT, }; use crate::watcher::Breach; @@ -486,9 +490,7 @@ mod tests_private_api { let (internal_api, _s) = create_api().await; // Add data to the Responser so we can retrieve it later on - internal_api - .watcher - .add_random_tracker_to_responder(generate_uuid()); + internal_api.watcher.add_random_tracker_to_responder(); let response = internal_api .get_all_appointments(Request::new(())) @@ -588,7 +590,7 @@ mod tests_private_api { ); internal_api .watcher - .add_dummy_tracker_to_responder(generate_uuid(), &tracker); + .add_dummy_tracker_to_responder(&tracker); } let locator = Locator::new(dispute_tx.txid()); @@ -655,9 +657,7 @@ mod tests_private_api { // And the Responder for _ in 0..3 { - internal_api - .watcher - .add_random_tracker_to_responder(generate_uuid()); + internal_api.watcher.add_random_tracker_to_responder(); } let response = internal_api @@ -730,12 +730,11 @@ mod tests_private_api { assert!(response.appointments.is_empty()); // Add an appointment and check back - let appointment = generate_dummy_appointment(None).inner; - let uuid = UUID::new(appointment.locator, user_id); - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + let user_signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); internal_api .watcher - .add_appointment(appointment.clone(), user_signature) + .add_appointment(appointment.inner, user_signature) .unwrap(); let response = internal_api @@ -786,10 +785,12 @@ mod tests_private_api { mod tests_public_api { use super::*; - use crate::extended_appointment::UUID; + use crate::responder::{ConfirmationStatus, TransactionTracker}; use crate::test_utils::{ - create_api, create_api_with_config, generate_dummy_appointment, ApiConfig, DURATION, SLOTS, + create_api, create_api_with_config, generate_dummy_appointment, get_random_tx, ApiConfig, + DURATION, SLOTS, }; + use crate::watcher::Breach; use teos_common::cryptography::{self, get_random_keypair}; #[tokio::test] @@ -900,12 +901,12 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); let response = internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + signature, })) .await .unwrap() @@ -925,12 +926,12 @@ mod tests_public_api { let (user_sk, _) = get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + signature, })) .await { @@ -954,12 +955,12 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + signature, })) .await { @@ -983,12 +984,12 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + signature, })) .await { @@ -1008,16 +1009,24 @@ mod tests_public_api { let user_id = UserId(user_pk); internal_api.watcher.register(user_id).unwrap(); - let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + // Add a tracker to the responder to simulate it being triggered. + let dispute_tx = get_random_tx(); + let tracker = TransactionTracker::new( + Breach::new(dispute_tx.clone(), get_random_tx()), + user_id, + ConfirmationStatus::ConfirmedIn(100), + ); internal_api - .watcher - .add_random_tracker_to_responder(UUID::new(appointment.locator, user_id)); + .get_watcher() + .add_dummy_tracker_to_responder(&tracker); + // Try to add it again using the API. + let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { - appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + appointment: Some(appointment.into()), + signature, })) .await { @@ -1038,12 +1047,12 @@ mod tests_public_api { let (user_sk, _) = get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.clone().into()), - signature: user_signature.clone(), + signature, })) .await { diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 16c3f39..ca6dabf 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -1,7 +1,7 @@ //! Logic related to the tower database manager (DBM), component in charge of persisting data on disk. //! -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::iter::FromIterator; use std::path::PathBuf; use std::str::FromStr; @@ -14,16 +14,15 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; use bitcoin::BlockHash; -use teos_common::appointment::{compute_appointment_slots, Appointment, Locator}; -use teos_common::constants::ENCRYPTED_BLOB_MAX_SIZE; +use teos_common::appointment::{Appointment, Locator}; use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; use teos_common::UserId; -use crate::extended_appointment::{AppointmentSummary, ExtendedAppointment, UUID}; +use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::UserInfo; -use crate::responder::{ConfirmationStatus, TrackerSummary, TransactionTracker}; +use crate::responder::{ConfirmationStatus, PenaltySummary, TransactionTracker}; -const TABLES: [&str; 5] = [ +const TABLES: [&str; 6] = [ "CREATE TABLE IF NOT EXISTS users ( user_id INT PRIMARY KEY, available_slots INT NOT NULL, @@ -59,6 +58,9 @@ const TABLES: [&str; 5] = [ "CREATE TABLE IF NOT EXISTS keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, key INT NOT NULL +)", + "CREATE INDEX IF NOT EXISTS locators_index ON appointments ( + locator )", ]; @@ -139,27 +141,21 @@ impl DBM { } } - /// Loads the associated appointments ([Appointment]) of a given user ([UserInfo]). - pub(crate) fn load_user_appointments(&self, user_id: UserId) -> HashMap { + /// Loads the associated locators ([Locator]) of a given user ([UserId]). + pub(crate) fn load_user_locators(&self, user_id: UserId) -> Vec { let mut stmt = self .connection - .prepare("SELECT UUID, encrypted_blob FROM appointments WHERE user_id=(?)") + .prepare("SELECT locator FROM appointments WHERE user_id=(?)") .unwrap(); - let mut rows = stmt.query([user_id.to_vec()]).unwrap(); - let mut appointments = HashMap::new(); - while let Ok(Some(inner_row)) = rows.next() { - let raw_uuid: Vec = inner_row.get(0).unwrap(); - let uuid = UUID::from_slice(&raw_uuid[0..20]).unwrap(); - let e_blob: Vec = inner_row.get(1).unwrap(); - - appointments.insert( - uuid, - compute_appointment_slots(e_blob.len(), ENCRYPTED_BLOB_MAX_SIZE), - ); - } - - appointments + stmt.query_map([user_id.to_vec()], |row| { + let raw_locator: Vec = row.get(0).unwrap(); + let locator = Locator::from_slice(&raw_locator).unwrap(); + Ok(locator) + }) + .unwrap() + .map(|res| res.unwrap()) + .collect() } /// Loads all users from the database. @@ -178,22 +174,14 @@ impl DBM { let start = row.get(2).unwrap(); let expiry = row.get(3).unwrap(); - users.insert( - user_id, - UserInfo::with_appointments( - slots, - start, - expiry, - self.load_user_appointments(user_id), - ), - ); + users.insert(user_id, UserInfo::new(slots, start, expiry)); } users } /// Removes some users from the database in batch. - pub(crate) fn batch_remove_users(&mut self, users: &HashSet) -> usize { + pub(crate) fn batch_remove_users(&mut self, users: &Vec) -> usize { let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; let tx = self.connection.transaction().unwrap(); let iter = users @@ -219,6 +207,24 @@ impl DBM { (users.len() as f64 / limit as f64).ceil() as usize } + /// Get the number of stored appointments. + pub(crate) fn get_appointments_count(&self) -> usize { + let mut stmt = self + .connection + .prepare("SELECT COUNT(*) FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL") + .unwrap(); + stmt.query_row([], |row| row.get(0)).unwrap() + } + + /// Get the number of stored trackers. + pub(crate) fn get_trackers_count(&self) -> usize { + let mut stmt = self + .connection + .prepare("SELECT COUNT(*) FROM trackers") + .unwrap(); + stmt.query_row([], |row| row.get(0)).unwrap() + } + /// Stores an [Appointment] into the database. pub(crate) fn store_appointment( &self, @@ -250,7 +256,11 @@ impl DBM { } /// Updates an existing [Appointment] in the database. - pub(crate) fn update_appointment(&self, uuid: UUID, appointment: &ExtendedAppointment) { + pub(crate) fn update_appointment( + &self, + uuid: UUID, + appointment: &ExtendedAppointment, + ) -> Result<(), Error> { // DISCUSS: Check what fields we'd like to make updatable. e_blob and signature are the obvious, to_self_delay and start_block may not be necessary (or even risky) let query = "UPDATE appointments SET encrypted_blob=(?1), to_self_delay=(?2), user_signature=(?3), start_block=(?4) WHERE UUID=(?5)"; @@ -266,9 +276,11 @@ impl DBM { ) { Ok(_) => { log::debug!("Appointment successfully updated: {uuid}"); + Ok(()) } - Err(_) => { - log::error!("Appointment not found, data cannot be updated: {uuid}"); + Err(e) => { + log::error!("Appointment not found, data cannot be updated: {uuid}. Error: {e:?}"); + Err(e) } } } @@ -305,32 +317,13 @@ impl DBM { .ok() } - /// Loads all [AppointmentSummary]s from that database. - pub(crate) fn load_appointment_summaries(&self) -> HashMap { - let mut summaries = HashMap::new(); - - let mut stmt = self - .connection - .prepare( - "SELECT a.UUID, a.locator, a.user_id - FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL", - ) - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let raw_uuid: Vec = row.get(0).unwrap(); - let raw_locator: Vec = row.get(1).unwrap(); - let raw_userid: Vec = row.get(2).unwrap(); - summaries.insert( - UUID::from_slice(&raw_uuid).unwrap(), - AppointmentSummary::new( - Locator::from_slice(&raw_locator).unwrap(), - UserId::from_slice(&raw_userid).unwrap(), - ), - ); - } - summaries + /// Check if an appointment with `uuid` exists. + pub(crate) fn appointment_exists(&self, uuid: UUID) -> bool { + self.connection + .prepare("SELECT UUID FROM appointments WHERE UUID=(?)") + .unwrap() + .exists([uuid.to_vec()]) + .unwrap() } /// Loads appointments from the database. If a locator is given, this method loads only the appointments @@ -380,6 +373,32 @@ impl DBM { appointments } + /// Gets the length of an appointment (the length of `appointment.encrypted_blob`). + pub(crate) fn get_appointment_length(&self, uuid: UUID) -> Option { + let mut stmt = self + .connection + .prepare("SELECT length(encrypted_blob) FROM appointments WHERE UUID=(?)") + .unwrap(); + + stmt.query_row([uuid.to_vec()], |row| row.get(0)).ok() + } + + /// Gets the [`UserId`] of the owner of the appointment along with the appointment + /// length (same as [DBM::get_appointment_length]) for `uuid`. + pub(crate) fn get_appointment_user_and_length(&self, uuid: UUID) -> Option<(UserId, usize)> { + let mut stmt = self + .connection + .prepare("SELECT user_id, length(encrypted_blob) FROM appointments WHERE UUID=(?)") + .unwrap(); + + stmt.query_row([uuid.to_vec()], |row| { + let raw_userid: Vec = row.get(0).unwrap(); + let length = row.get(1).unwrap(); + Ok((UserId::from_slice(&raw_userid).unwrap(), length)) + }) + .ok() + } + /// Removes an [Appointment] from the database. pub(crate) fn remove_appointment(&self, uuid: UUID) { let query = "DELETE FROM appointments WHERE UUID=(?)"; @@ -393,11 +412,12 @@ impl DBM { } } - /// Removes some appointments from the database in batch and updates the associated users giving back - /// the freed appointment slots + /// Removes some appointments from the database in batch and updates the associated users + /// (giving back freed appointment slots) in one transaction so that the deletion and the + /// update is atomic. pub(crate) fn batch_remove_appointments( &mut self, - appointments: &HashSet, + appointments: &Vec, updated_users: &HashMap, ) -> usize { let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; @@ -433,18 +453,49 @@ impl DBM { (appointments.len() as f64 / limit as f64).ceil() as usize } - /// Loads the locator associated to a given UUID - pub(crate) fn load_locator(&self, uuid: UUID) -> Option { + /// Loads the [`UUID`]s of appointments triggered by `locator`. + pub(crate) fn load_uuids(&self, locator: Locator) -> Vec { let mut stmt = self .connection - .prepare("SELECT locator FROM appointments WHERE UUID=(?)") + .prepare("SELECT UUID from appointments WHERE locator=(?)") .unwrap(); - stmt.query_row([uuid.to_vec()], |row| { - let raw_locator: Vec = row.get(0).unwrap(); - Ok(Locator::from_slice(&raw_locator).unwrap()) + stmt.query_map([locator.to_vec()], |row| { + let raw_uuid: Vec = row.get(0).unwrap(); + let uuid = UUID::from_slice(&raw_uuid).unwrap(); + Ok(uuid) }) - .ok() + .unwrap() + .map(|uuid_res| uuid_res.unwrap()) + .collect() + } + + /// Filters the given set of [`Locator`]s by including only the ones which trigger any of our stored appointments. + pub(crate) fn batch_check_locators_exist(&self, locators: Vec<&Locator>) -> Vec { + let mut registered_locators = Vec::new(); + let locators: Vec> = locators.iter().map(|l| l.to_vec()).collect(); + let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; + + for chunk in locators.chunks(limit) { + let query = "SELECT locator FROM appointments WHERE locator IN ".to_owned(); + let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); + + let mut stmt = self + .connection + .prepare(&format!("{query}{placeholders}")) + .unwrap(); + let known_locators = stmt + .query_map(params_from_iter(chunk), |row| { + let raw_locator: Vec = row.get(0).unwrap(); + let locator = Locator::from_slice(&raw_locator).unwrap(); + Ok(locator) + }) + .unwrap() + .map(|locator_res| locator_res.unwrap()); + registered_locators.extend(known_locators); + } + + registered_locators } /// Stores a [TransactionTracker] into the database. @@ -478,6 +529,29 @@ impl DBM { } } + /// Updates the tracker status in the database. + /// + /// The only updatable fields are `height` and `confirmed`. + pub(crate) fn update_tracker_status( + &self, + uuid: UUID, + status: &ConfirmationStatus, + ) -> Result<(), Error> { + let (height, confirmed) = status.to_db_data().ok_or(Error::MissingField)?; + + let query = "UPDATE trackers SET height=(?1), confirmed=(?2) WHERE UUID=(?3)"; + match self.update_data(query, params![height, confirmed, uuid.to_vec(),]) { + Ok(x) => { + log::debug!("Tracker successfully updated: {uuid}"); + Ok(x) + } + Err(e) => { + log::error!("Couldn't update tracker: {uuid}. Error: {e:?}"); + Err(e) + } + } + } + /// Loads a [TransactionTracker] from the database. pub(crate) fn load_tracker(&self, uuid: UUID) -> Option { let key = uuid.to_vec(); @@ -509,37 +583,13 @@ impl DBM { .ok() } - /// Loads all [TrackerSummary]s from that database. - pub(crate) fn load_tracker_summaries(&self) -> HashMap { - let mut summaries = HashMap::new(); - - let mut stmt = self - .connection - .prepare( - "SELECT t.UUID, t.penalty_tx, t.height, t.confirmed, a.user_id - FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID", - ) - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let raw_uuid: Vec = row.get(0).unwrap(); - let raw_penalty_tx: Vec = row.get(1).unwrap(); - let height: u32 = row.get(2).unwrap(); - let confirmed: bool = row.get(3).unwrap(); - let raw_userid: Vec = row.get(4).unwrap(); - summaries.insert( - UUID::from_slice(&raw_uuid).unwrap(), - TrackerSummary::new( - UserId::from_slice(&raw_userid).unwrap(), - consensus::deserialize::(&raw_penalty_tx) - .unwrap() - .txid(), - ConfirmationStatus::from_db_data(height, confirmed), - ), - ); - } - summaries + /// Check if a tracker with `uuid` exists. + pub(crate) fn tracker_exists(&self, uuid: UUID) -> bool { + self.connection + .prepare("SELECT UUID FROM trackers WHERE UUID=(?)") + .unwrap() + .exists([uuid.to_vec()]) + .unwrap() } /// Loads trackers from the database. If a locator is given, this method loads only the trackers @@ -591,6 +641,66 @@ impl DBM { trackers } + /// Loads trackers with the given confirmation status. + /// + /// Note that for [`ConfirmationStatus::InMempoolSince(height)`] variant, this pulls trackers + /// with `h <= height` and not just `h = height`. + pub(crate) fn load_trackers_with_confirmation_status( + &self, + status: ConfirmationStatus, + ) -> Result, Error> { + let (height, confirmed) = status.to_db_data().ok_or(Error::MissingField)?; + let sql = format!( + "SELECT UUID FROM trackers WHERE confirmed=(?1) AND height{}(?2)", + if confirmed { "=" } else { "<=" } + ); + let mut stmt = self.connection.prepare(&sql).unwrap(); + + Ok(stmt + .query_map(params![confirmed, height], |row| { + let raw_uuid: Vec = row.get(0).unwrap(); + let uuid = UUID::from_slice(&raw_uuid).unwrap(); + Ok(uuid) + }) + .unwrap() + .map(|uuid_res| uuid_res.unwrap()) + .collect()) + } + + /// Loads the transaction IDs of all the penalties and their status from the database. + pub(crate) fn load_penalties_summaries(&self) -> HashMap { + let mut summaries = HashMap::new(); + + let mut stmt = self + .connection + .prepare( + "SELECT t.UUID, t.penalty_tx, t.height, t.confirmed + FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID", + ) + .unwrap(); + let mut rows = stmt.query([]).unwrap(); + + while let Ok(Some(row)) = rows.next() { + let raw_uuid: Vec = row.get(0).unwrap(); + let raw_penalty_tx: Vec = row.get(1).unwrap(); + let height: u32 = row.get(2).unwrap(); + let confirmed: bool = row.get(3).unwrap(); + + // DISCUSS: Should we store the txids to avoid pulling raw txs and deserializing then hashing them. + let penalty_txid = consensus::deserialize::(&raw_penalty_tx) + .unwrap() + .txid(); + summaries.insert( + UUID::from_slice(&raw_uuid).unwrap(), + PenaltySummary::new( + penalty_txid, + ConfirmationStatus::from_db_data(height, confirmed), + ), + ); + } + summaries + } + /// Stores the last known block into the database. pub(crate) fn store_last_known_block(&self, block_hash: &BlockHash) -> Result<(), Error> { let query = "INSERT OR REPLACE INTO last_known_block (id, block_hash) VALUES (0, ?)"; @@ -642,11 +752,13 @@ impl DBM { #[cfg(test)] mod tests { use super::*; + use std::collections::HashSet; use std::iter::FromIterator; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; - use teos_common::test_utils::get_random_user_id; + use teos_common::test_utils::{get_random_locator, get_random_user_id}; + use crate::rpc_errors; use crate::test_utils::{ generate_dummy_appointment, generate_dummy_appointment_with_user, generate_uuid, get_random_tracker, get_random_tx, AVAILABLE_SLOTS, SUBSCRIPTION_EXPIRY, @@ -668,20 +780,15 @@ mod tests { let mut stmt = self .connection .prepare( - "SELECT user_id, available_slots, subscription_start, subscription_expiry + "SELECT available_slots, subscription_start, subscription_expiry FROM users WHERE user_id=(?)", ) .unwrap(); stmt.query_row([&key], |row| { - let slots = row.get(1).unwrap(); - let start = row.get(2).unwrap(); - let expiry = row.get(3).unwrap(); - Ok(UserInfo::with_appointments( - slots, - start, - expiry, - self.load_user_appointments(user_id), - )) + let slots = row.get(0).unwrap(); + let start = row.get(1).unwrap(); + let expiry = row.get(2).unwrap(); + Ok(UserInfo::new(slots, start, expiry)) }) .ok() } @@ -712,27 +819,6 @@ mod tests { )); } - #[test] - fn test_store_load_user_with_appointments() { - let dbm = DBM::in_memory().unwrap(); - - let user_id = get_random_user_id(); - let mut user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - - dbm.store_user(user_id, &user).unwrap(); - - // Add some appointments to the user - for _ in 0..10 { - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); - user.appointments.insert(uuid, 1); - } - - // Check both loading the whole user info or only the associated appointments - assert_eq!(dbm.load_user(user_id).unwrap(), user); - assert_eq!(dbm.load_user_appointments(user_id), user.appointments); - } - #[test] fn test_load_nonexistent_user() { let dbm = DBM::in_memory().unwrap(); @@ -756,6 +842,30 @@ mod tests { assert_eq!(dbm.load_user(user_id).unwrap(), user); } + #[test] + fn test_load_user_locators() { + let dbm = DBM::in_memory().unwrap(); + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user).unwrap(); + + let mut locators = HashSet::new(); + + // Add some appointments to the user + for _ in 0..10 { + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + locators.insert(appointment.locator()); + } + + assert_eq!(dbm.load_user(user_id).unwrap(), user); + assert_eq!( + HashSet::from_iter(dbm.load_user_locators(user_id)), + locators + ); + } + #[test] fn test_load_all_users() { let dbm = DBM::in_memory().unwrap(); @@ -768,19 +878,8 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - users.insert(user_id, user.clone()); + users.insert(user_id, user); dbm.store_user(user_id, &user).unwrap(); - - // Add appointments to some of the users - if i % 2 == 0 { - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); - users - .get_mut(&user_id) - .unwrap() - .appointments - .insert(uuid, 1); - } } assert_eq!(dbm.load_all_users(), users); @@ -796,7 +895,7 @@ mod tests { dbm.connection .set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, limit); - let mut to_be_deleted = HashSet::new(); + let mut to_be_deleted = Vec::new(); let mut rest = HashSet::new(); for i in 1..100 { let user_id = get_random_user_id(); @@ -804,7 +903,7 @@ mod tests { dbm.store_user(user_id, &user).unwrap(); if i % 2 == 0 { - to_be_deleted.insert(user_id); + to_be_deleted.push(user_id); } else { rest.insert(user_id); } @@ -836,7 +935,7 @@ mod tests { Ok { .. } )); - dbm.batch_remove_users(&HashSet::from_iter(vec![appointment.user_id])); + dbm.batch_remove_users(&vec![appointment.user_id]); assert!(dbm.load_user(appointment.user_id).is_none()); assert!(dbm.load_appointment(uuid).is_none()); @@ -848,7 +947,7 @@ mod tests { )); assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); - dbm.batch_remove_users(&HashSet::from_iter(vec![appointment.user_id])); + dbm.batch_remove_users(&vec![appointment.user_id]); assert!(dbm.load_user(appointment.user_id).is_none()); assert!(dbm.load_appointment(uuid).is_none()); assert!(dbm.load_tracker(uuid).is_none()); @@ -863,6 +962,37 @@ mod tests { dbm.batch_remove_users(&users); } + #[test] + fn test_get_appointments_trackers_count() { + let dbm = DBM::in_memory().unwrap(); + let n_users = 100; + let n_app_per_user = 4; + let n_trk_per_user = 6; + + for _ in 0..n_users { + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user).unwrap(); + + // These are un-triggered appointments. + for _ in 0..n_app_per_user { + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + } + + // And these are triggered ones (trackers). + for _ in 0..n_trk_per_user { + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + let tracker = get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)); + dbm.store_tracker(uuid, &tracker).unwrap(); + } + } + + assert_eq!(dbm.get_appointments_count(), n_users * n_app_per_user); + assert_eq!(dbm.get_trackers_count(), n_users * n_trk_per_user); + } + #[test] fn test_store_load_appointment() { let dbm = DBM::in_memory().unwrap(); @@ -909,6 +1039,22 @@ mod tests { assert!(dbm.load_appointment(uuid).is_none()); } + #[test] + fn test_appointment_exists() { + let dbm = DBM::in_memory().unwrap(); + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + + assert!(!dbm.appointment_exists(uuid)); + + dbm.store_user(user_id, &user).unwrap(); + dbm.store_appointment(uuid, &appointment).unwrap(); + + assert!(dbm.appointment_exists(uuid)); + } + #[test] fn test_update_appointment() { let dbm = DBM::in_memory().unwrap(); @@ -932,7 +1078,8 @@ mod tests { another_modified_appointment.user_id = get_random_user_id(); // Check how only the modifiable fields have been updated - dbm.update_appointment(uuid, &another_modified_appointment); + dbm.update_appointment(uuid, &another_modified_appointment) + .unwrap(); assert_eq!(dbm.load_appointment(uuid).unwrap(), modified_appointment); assert_ne!( dbm.load_appointment(uuid).unwrap(), @@ -1031,6 +1178,44 @@ mod tests { assert_eq!(dbm.load_appointments(Some(locator)), appointments); } + #[test] + fn test_get_appointment_length() { + let dbm = DBM::in_memory().unwrap(); + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + + dbm.store_user(user_id, &user).unwrap(); + dbm.store_appointment(uuid, &appointment).unwrap(); + + assert_eq!( + dbm.get_appointment_length(uuid).unwrap(), + appointment.inner.encrypted_blob.len() + ); + assert!(dbm.get_appointment_length(generate_uuid()).is_none()); + } + + #[test] + fn test_get_appointment_user_and_length() { + let dbm = DBM::in_memory().unwrap(); + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + + dbm.store_user(user_id, &user).unwrap(); + dbm.store_appointment(uuid, &appointment).unwrap(); + + assert_eq!( + dbm.get_appointment_user_and_length(uuid).unwrap(), + (user_id, appointment.encrypted_blob().len()) + ); + assert!(dbm + .get_appointment_user_and_length(generate_uuid()) + .is_none()); + } + #[test] fn test_batch_remove_appointments() { let mut dbm = DBM::in_memory().unwrap(); @@ -1051,13 +1236,13 @@ mod tests { let mut rest = HashSet::new(); for i in 1..6 { - let mut to_be_deleted = HashSet::new(); + let mut to_be_deleted = Vec::new(); for j in 0..limit * 2 * i { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); dbm.store_appointment(uuid, &appointment).unwrap(); if j % 2 == 0 { - to_be_deleted.insert(uuid); + to_be_deleted.push(uuid); } else { rest.insert(uuid); } @@ -1066,7 +1251,7 @@ mod tests { // When the appointment are deleted, the user will get back slots based on the deleted data. // Here we can just make a number up to make sure it matches. user.available_slots = i as u32; - let updated_users = HashMap::from_iter([(user_id, user.clone())]); + let updated_users = HashMap::from_iter([(user_id, user)]); // Check that the db transaction had i queries on it assert_eq!( @@ -1102,8 +1287,8 @@ mod tests { )); dbm.batch_remove_appointments( - &HashSet::from_iter(vec![uuid]), - &HashMap::from_iter([(appointment.user_id, info.clone())]), + &vec![uuid], + &HashMap::from_iter([(appointment.user_id, info)]), ); assert!(dbm.load_appointment(uuid).is_none()); @@ -1115,7 +1300,7 @@ mod tests { assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); dbm.batch_remove_appointments( - &HashSet::from_iter(vec![uuid]), + &vec![uuid], &HashMap::from_iter([(appointment.user_id, info)]), ); assert!(dbm.load_appointment(uuid).is_none()); @@ -1130,32 +1315,83 @@ mod tests { // Test it does not fail even if the user does not exist (it will log though) dbm.batch_remove_appointments(&appointments, &HashMap::new()); } + #[test] - fn test_load_locator() { + fn test_load_uuids() { let dbm = DBM::in_memory().unwrap(); - // In order to add an appointment we need the associated user to be present - let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + let dispute_tx = get_random_tx(); + let dispute_txid = dispute_tx.txid(); + let mut uuids = HashSet::new(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + // Add ten appointments triggered by the same locator. + for _ in 0..10 { + let user_id = get_random_user_id(); + dbm.store_user(user_id, &user).unwrap(); - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); + dbm.store_appointment(uuid, &appointment).unwrap(); - // We should be able to load the locator now the appointment exists - assert_eq!(dbm.load_locator(uuid).unwrap(), appointment.locator()); + uuids.insert(uuid); + } + + // Add ten more appointments triggered by different locators. + for _ in 0..10 { + let user_id = get_random_user_id(); + dbm.store_user(user_id, &user).unwrap(); + + let dispute_txid = get_random_tx().txid(); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); + dbm.store_appointment(uuid, &appointment).unwrap(); + } + + assert_eq!( + HashSet::from_iter(dbm.load_uuids(Locator::new(dispute_txid))), + uuids + ); } #[test] - fn test_load_nonexistent_locator() { + fn test_batch_check_locators_exist() { let dbm = DBM::in_memory().unwrap(); + // Generate `n_app` appointments which we will store in the DB. + let n_app = 100; + let appointments: Vec<_> = (0..n_app) + .map(|_| generate_dummy_appointment(None)) + .collect(); - let (uuid, _) = generate_dummy_appointment_with_user(get_random_user_id(), None); - assert!(dbm.load_locator(uuid).is_none()); + // Register all the users beforehand. + for user_id in appointments.iter().map(|a| a.user_id) { + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user).unwrap(); + } + + // Store all the `n_app` appointments. + for appointment in appointments.iter() { + dbm.store_appointment(appointment.uuid(), appointment) + .unwrap(); + } + + // Select `n_app / 5` locators as if they appeared in a new block. + let known_locators: HashSet<_> = appointments + .iter() + .take(n_app / 5) + .map(|a| a.locator()) + .collect(); + // And extra `n_app / 5` unknown locators. + let unknown_locators: HashSet<_> = (0..n_app / 5).map(|_| get_random_locator()).collect(); + let all_locators = known_locators + .iter() + .chain(unknown_locators.iter()) + .collect(); + + assert_eq!( + HashSet::from_iter(dbm.batch_check_locators_exist(all_locators)), + known_locators + ); } #[test] @@ -1215,6 +1451,38 @@ mod tests { )); } + #[test] + fn test_update_tracker_status() { + let dbm = DBM::in_memory().unwrap(); + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user).unwrap(); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + + let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(42)); + dbm.store_tracker(uuid, &tracker).unwrap(); + + // Update the status and check if it's actually updated. + dbm.update_tracker_status(uuid, &ConfirmationStatus::ConfirmedIn(100)) + .unwrap(); + assert_eq!( + dbm.load_tracker(uuid).unwrap().status, + ConfirmationStatus::ConfirmedIn(100) + ); + + // Rejected status doesn't have a persistent DB representation. + assert!(matches!( + dbm.update_tracker_status( + uuid, + &ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_REJECTED) + ), + Err(Error::MissingField) + )); + } + #[test] fn test_load_nonexistent_tracker() { let dbm = DBM::in_memory().unwrap(); @@ -1285,6 +1553,166 @@ mod tests { assert_eq!(dbm.load_trackers(Some(locator)), trackers); } + #[test] + fn test_load_trackers_with_confirmation_status_in_mempool() { + let dbm = DBM::in_memory().unwrap(); + let n_trackers = 100; + let mut tracker_statuses = HashMap::new(); + + // Store a bunch of trackers. + for i in 0..n_trackers { + let user_id = get_random_user_id(); + let user = UserInfo::new( + AVAILABLE_SLOTS + i, + SUBSCRIPTION_START + i, + SUBSCRIPTION_EXPIRY + i, + ); + dbm.store_user(user_id, &user).unwrap(); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + + // Some trackers confirmed and some aren't. + let status = if i % 2 == 0 { + ConfirmationStatus::InMempoolSince(i) + } else { + ConfirmationStatus::ConfirmedIn(i) + }; + + let tracker = get_random_tracker(user_id, status); + dbm.store_tracker(uuid, &tracker).unwrap(); + tracker_statuses.insert(uuid, status); + } + + for i in 0..n_trackers + 10 { + let in_mempool_since_i: HashSet = tracker_statuses + .iter() + .filter_map(|(&uuid, &status)| { + if let ConfirmationStatus::InMempoolSince(x) = status { + // If a tracker was in mempool since x, then it's also in mempool since x + 1, x + 2, etc... + return (x <= i).then_some(uuid); + } + None + }) + .collect(); + assert_eq!( + HashSet::from_iter( + dbm.load_trackers_with_confirmation_status(ConfirmationStatus::InMempoolSince( + i + )) + .unwrap() + ), + in_mempool_since_i, + ); + } + } + + #[test] + fn test_load_trackers_with_confirmation_status_confirmed() { + let dbm = DBM::in_memory().unwrap(); + let n_blocks = 100; + let n_trackers = 30; + let mut tracker_statuses = HashMap::new(); + + // Loop over a bunch of blocks. + for i in 0..n_blocks { + // Store a bunch of trackers in each block. + for j in 0..n_trackers { + let user_id = get_random_user_id(); + let user = UserInfo::new( + AVAILABLE_SLOTS + i, + SUBSCRIPTION_START + i, + SUBSCRIPTION_EXPIRY + i, + ); + dbm.store_user(user_id, &user).unwrap(); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + + // Some trackers confirmed and some aren't. + let status = if j % 2 == 0 { + ConfirmationStatus::InMempoolSince(i) + } else { + ConfirmationStatus::ConfirmedIn(i) + }; + + let tracker = get_random_tracker(user_id, status); + dbm.store_tracker(uuid, &tracker).unwrap(); + tracker_statuses.insert(uuid, status); + } + } + + for i in 0..n_blocks + 10 { + let confirmed_in_i: HashSet = tracker_statuses + .iter() + .filter_map(|(&uuid, &status)| { + if let ConfirmationStatus::ConfirmedIn(x) = status { + return (x == i).then_some(uuid); + } + None + }) + .collect(); + assert_eq!( + HashSet::from_iter( + dbm.load_trackers_with_confirmation_status(ConfirmationStatus::ConfirmedIn(i)) + .unwrap() + ), + confirmed_in_i, + ); + } + } + + #[test] + fn test_load_trackers_with_confirmation_status_bad_status() { + let dbm = DBM::in_memory().unwrap(); + + assert!(matches!( + dbm.load_trackers_with_confirmation_status(ConfirmationStatus::Rejected( + rpc_errors::RPC_VERIFY_REJECTED + )), + Err(Error::MissingField) + )); + + assert!(matches!( + dbm.load_trackers_with_confirmation_status(ConfirmationStatus::IrrevocablyResolved), + Err(Error::MissingField) + )); + } + + #[test] + fn test_load_penalties_summaries() { + let dbm = DBM::in_memory().unwrap(); + let n_trackers = 100; + let mut penalties_summaries = HashMap::new(); + + for i in 0..n_trackers { + let user_id = get_random_user_id(); + let user = UserInfo::new( + AVAILABLE_SLOTS + i, + SUBSCRIPTION_START + i, + SUBSCRIPTION_EXPIRY + i, + ); + dbm.store_user(user_id, &user).unwrap(); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).unwrap(); + + let status = if i % 2 == 0 { + ConfirmationStatus::InMempoolSince(i) + } else { + ConfirmationStatus::ConfirmedIn(i) + }; + + let tracker = get_random_tracker(user_id, status); + dbm.store_tracker(uuid, &tracker).unwrap(); + + penalties_summaries + .insert(uuid, PenaltySummary::new(tracker.penalty_tx.txid(), status)); + } + + assert_eq!(dbm.load_penalties_summaries(), penalties_summaries); + } + #[test] fn test_store_load_last_known_block() { let dbm = DBM::in_memory().unwrap(); diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index 32cc0b9..e7ba8f4 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -46,8 +46,6 @@ impl std::fmt::Display for UUID { /// An extended version of the appointment hold by the tower. /// /// The [Appointment] is extended in terms of data, that is, it provides further information only relevant to the tower. -/// Notice [ExtendedAppointment]s are not kept in memory but persisted on disk. The [Watcher](crate::watcher::Watcher) -/// keeps [AppointmentSummary] instead. #[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct ExtendedAppointment { /// The underlying appointment extended by [ExtendedAppointment]. @@ -60,24 +58,6 @@ pub(crate) struct ExtendedAppointment { pub start_block: u32, } -/// A summary of an appointment. -/// -/// Contains the minimal amount of data the [Watcher](crate::watcher::Watcher) needs to keep in memory in order to -/// watch for breaches. -#[derive(Debug, Eq, PartialEq, Clone)] -pub(crate) struct AppointmentSummary { - /// The [Appointment] locator. - pub locator: Locator, - /// The user this [Appointment] belongs to. - pub user_id: UserId, -} - -impl AppointmentSummary { - pub fn new(locator: Locator, user_id: UserId) -> Self { - Self { locator, user_id } - } -} - impl ExtendedAppointment { /// Create a new [ExtendedAppointment]. pub fn new( @@ -109,12 +89,8 @@ impl ExtendedAppointment { self.inner.to_self_delay } - /// Computes the summary of the [ExtendedAppointment]. - pub fn get_summary(&self) -> AppointmentSummary { - AppointmentSummary { - locator: self.locator(), - user_id: self.user_id, - } + pub fn uuid(&self) -> UUID { + UUID::new(self.inner.locator, self.user_id) } } @@ -122,22 +98,14 @@ impl ExtendedAppointment { mod tests { use super::*; - use teos_common::appointment::Appointment; - use teos_common::cryptography::get_random_bytes; - use teos_common::test_utils::get_random_user_id; + use crate::test_utils::generate_uuid; #[test] - fn test_get_summary() { - let locator = Locator::from_slice(&get_random_bytes(16)).unwrap(); - let user_id = get_random_user_id(); - let signature = String::new(); - - let a = Appointment::new(locator, get_random_bytes(32), 42); - let e = ExtendedAppointment::new(a, user_id, signature, 21); - - let s = e.get_summary(); - - assert_eq!(e.locator(), s.locator); - assert_eq!(e.user_id, s.user_id); + fn test_uuid_ser_deser() { + let original_uuid = generate_uuid(); + assert_eq!( + UUID::from_slice(&original_uuid.to_vec()).unwrap(), + original_uuid + ); } } diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 3106991..e5d490e 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -1,13 +1,11 @@ //! Logic related to the Gatekeeper, the component in charge of managing access to the tower resources. -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; +use lightning::chain; +use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; -use lightning::chain; - -use teos_common::appointment::compute_appointment_slots; +use teos_common::appointment::{compute_appointment_slots, Locator}; use teos_common::constants::ENCRYPTED_BLOB_MAX_SIZE; use teos_common::cryptography; use teos_common::receipts::RegistrationReceipt; @@ -17,7 +15,7 @@ use crate::dbm::DBM; use crate::extended_appointment::{ExtendedAppointment, UUID}; /// Data regarding a user subscription with the tower. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct UserInfo { /// Number of appointment slots available for a given user. pub(crate) available_slots: u32, @@ -25,8 +23,6 @@ pub(crate) struct UserInfo { pub(crate) subscription_start: u32, /// Block height where the user subscription expires. pub(crate) subscription_expiry: u32, - /// Map of appointment ids and the how many slots they take from the subscription. - pub(crate) appointments: HashMap, } impl UserInfo { @@ -36,22 +32,6 @@ impl UserInfo { available_slots, subscription_start, subscription_expiry, - appointments: HashMap::new(), - } - } - - /// Creates a new [UserInfo] instance with some associated appointments. - pub fn with_appointments( - available_slots: u32, - subscription_start: u32, - subscription_expiry: u32, - appointments: HashMap, - ) -> Self { - UserInfo { - available_slots, - subscription_start, - subscription_expiry, - appointments, } } } @@ -134,8 +114,9 @@ impl Gatekeeper { } /// Gets the data held by the tower about a given user. - pub(crate) fn get_user_info(&self, user_id: UserId) -> Option { - self.registered_users.lock().unwrap().get(&user_id).cloned() + pub(crate) fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { + let info = self.registered_users.lock().unwrap().get(&user_id).cloned(); + info.map(|info| (info, self.dbm.lock().unwrap().load_user_locators(user_id))) } /// Authenticates a user. @@ -219,7 +200,13 @@ impl Gatekeeper { // For updates, the difference between the existing appointment size and the update is computed. let mut registered_users = self.registered_users.lock().unwrap(); let user_info = registered_users.get_mut(&user_id).unwrap(); - let used_slots = user_info.appointments.get(&uuid).map_or(0, |x| *x); + let used_blob_size = self + .dbm + .lock() + .unwrap() + .get_appointment_length(uuid) + .unwrap_or(0); + let used_slots = compute_appointment_slots(used_blob_size, ENCRYPTED_BLOB_MAX_SIZE); let required_slots = compute_appointment_slots(appointment.encrypted_blob().len(), ENCRYPTED_BLOB_MAX_SIZE); @@ -228,7 +215,6 @@ impl Gatekeeper { if diff <= user_info.available_slots as i64 { // Filling / freeing slots depending on whether this is an update or not, and if it is bigger or smaller // than the old appointment - user_info.appointments.insert(uuid, required_slots); user_info.available_slots = (user_info.available_slots as i64 - diff) as u32; self.dbm.lock().unwrap().update_user(user_id, user_info); @@ -258,56 +244,50 @@ impl Gatekeeper { /// Gets a map of outdated users. Outdated users are those whose subscription has expired and the renewal grace period /// has already passed ([expiry_delta](Self::expiry_delta)). - pub(crate) fn get_outdated_users(&self, block_height: u32) -> HashMap> { - let registered_users = self.registered_users.lock().unwrap().clone(); - registered_users - .into_iter() - .filter(|(_, info)| block_height == info.subscription_expiry + self.expiry_delta) - .map(|(id, info)| (id, info.appointments.keys().cloned().collect())) + pub(crate) fn get_outdated_users(&self, block_height: u32) -> Vec { + self.registered_users + .lock() + .unwrap() + .iter() + // NOTE: Ideally there won't be a user with `block_height > subscription_expiry + expiry_delta`, but + // this might happen if we skip a couple of block connections due to a force update. + .filter(|(_, info)| block_height >= info.subscription_expiry + self.expiry_delta) + .map(|(user_id, _)| *user_id) .collect() } - /// Gets a set of outdated user ids. - pub(crate) fn get_outdated_user_ids(&self, block_height: u32) -> HashSet { - self.get_outdated_users(block_height) - .keys() - .cloned() - .collect() - } - - /// Get a map of outdated appointments (from any user). - pub(crate) fn get_outdated_appointments(&self, block_height: u32) -> HashSet { - HashSet::from_iter( - self.get_outdated_users(block_height) - .into_values() - .flatten(), - ) - } - - /// Deletes a collection of appointments from the users' subscriptions (from memory only) - /// and updates the available_slots count for the given user. + /// Deletes these appointments from the database and updates the user's information. /// - /// Notice appointments are only de-linked from users, but not actually removed. This is because the [Gatekeeper] - /// does not actually hold any [ExtendedAppointment](crate::extended_appointment::ExtendedAppointment) data, - /// just references to them. - pub(crate) fn delete_appointments_from_memory( - &self, - appointments: &HashMap, - ) -> HashMap { - let mut updated_users = HashMap::new(); - let mut registered_users = self.registered_users.lock().unwrap(); + /// If `refund` is set, the appointments owners will get their slots refunded back. + /// + /// DISCUSS: When `refund` is `false` we don't give back the slots to the user for the deleted appointments. + /// This is to discourage misbehavior (sending bad appointments, either non-decryptable or rejected by the network). + pub(crate) fn delete_appointments(&self, appointments: Vec, refund: bool) { + let mut dbm = self.dbm.lock().unwrap(); - for (uuid, user_id) in appointments { - // Remove the appointment from the appointment list and update the available slots - if let Some(user_info) = registered_users.get_mut(user_id) { - if let Some(x) = user_info.appointments.remove(uuid) { - user_info.available_slots += x; - } - updated_users.insert(*user_id, user_info.clone()); - }; + let updated_users = if refund { + let mut updated_users = HashMap::new(); + let mut registered_users = self.registered_users.lock().unwrap(); + // Give back the consumed slots to each user. + for uuid in appointments.iter() { + let (user_id, blob_size) = dbm.get_appointment_user_and_length(*uuid).unwrap(); + registered_users.get_mut(&user_id).unwrap().available_slots += + compute_appointment_slots(blob_size, ENCRYPTED_BLOB_MAX_SIZE); + updated_users.insert(user_id, registered_users[&user_id]); + } + updated_users + } else { + // No updated users. + HashMap::new() + }; + + // An optimization for the case when only one appointment is being deleted without refunding. + // This avoids creating a DB transaction for a single query. + if appointments.len() == 1 && updated_users.is_empty() { + dbm.remove_appointment(appointments[0]) + } else { + dbm.batch_remove_appointments(&appointments, &updated_users); } - - updated_users } } @@ -324,12 +304,17 @@ impl chain::Listen for Gatekeeper { log::info!("New block received: {}", header.block_hash()); // Expired user deletion is delayed. Users are deleted when their subscription is outdated, not expired. - let outdated_users = self.get_outdated_user_ids(height); + let outdated_users = self.get_outdated_users(height); if !outdated_users.is_empty() { - self.registered_users - .lock() - .unwrap() - .retain(|id, _| !outdated_users.contains(id)); + // Remove the outdated users from memory first. + { + let mut registered_users = self.registered_users.lock().unwrap(); + // Removing each outdated user in a loop is more efficient than retaining non-outdated users + // because retaining would loop over all the available users which is always more than the outdated ones. + for outdated_user in outdated_users.iter() { + registered_users.remove(outdated_user); + } + } self.dbm.lock().unwrap().batch_remove_users(&outdated_users); } @@ -351,13 +336,13 @@ impl chain::Listen for Gatekeeper { mod tests { use super::*; - use crate::test_utils::{ - generate_dummy_appointment, generate_dummy_appointment_with_user, generate_uuid, Blockchain, - }; + use crate::test_utils::{generate_dummy_appointment_with_user, get_random_tracker, Blockchain}; use lightning::chain::Listen; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::test_utils::get_random_user_id; + use crate::responder::ConfirmationStatus; + const SLOTS: u32 = 21; const DURATION: u32 = 500; const EXPIRY_DELTA: u32 = 42; @@ -380,21 +365,11 @@ mod tests { &self.registered_users } - pub(crate) fn add_outdated_user( - &self, - user_id: UserId, - outdates_at: u32, - appointments: Option>, - ) { + pub(crate) fn add_outdated_user(&self, user_id: UserId, outdates_at: u32) { self.add_update_user(user_id).unwrap(); let mut registered_users = self.registered_users.lock().unwrap(); let user = registered_users.get_mut(&user_id).unwrap(); user.subscription_expiry = outdates_at - self.expiry_delta; - if let Some(uuids) = appointments { - for uuid in uuids.iter() { - user.appointments.insert(*uuid, 1); - } - } } } @@ -567,25 +542,32 @@ mod tests { let available_slots = gatekeeper .add_update_appointment(user_id, uuid, &appointment) .unwrap(); + // Simulate the watcher adding the appointment in the database. + gatekeeper + .dbm + .lock() + .unwrap() + .store_appointment(uuid, &appointment) + .unwrap(); - assert!(gatekeeper.registered_users.lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid)); + let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + assert!(user_locators.contains(&appointment.locator())); assert_eq!(slots_before, available_slots + 1); - // Slots should have been updated in the database too. Notice the appointment won't be there yet - // given the Watcher is responsible for adding it, and it will do so after calling this method + // Slots should have been updated in the database too. let mut loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, available_slots); - // Adding the exact same appointment should leave the slots count unchanged + // Adding the exact same appointment should leave the slots count unchanged. + // We don't really need to update the appointment in the DB since it's the very same appointment. let mut updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &appointment) .unwrap(); - assert!(gatekeeper.registered_users.lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid)); + + let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots); + loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); @@ -595,10 +577,18 @@ mod tests { updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &bigger_appointment) .unwrap(); - assert!(gatekeeper.registered_users.lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid)); + // Simulate the watcher updating the appointment in the database. + gatekeeper + .dbm + .lock() + .unwrap() + .update_appointment(uuid, &bigger_appointment) + .unwrap(); + + let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots - 1); + loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); @@ -606,26 +596,43 @@ mod tests { updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &appointment) .unwrap(); - assert!(gatekeeper.registered_users.lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid)); + // Simulate the watcher updating the appointment in the database. + gatekeeper + .dbm + .lock() + .unwrap() + .update_appointment(uuid, &appointment) + .unwrap(); + + let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots); + loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // Adding an appointment with a different uuid should not count as an update - let new_uuid = generate_uuid(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); updated_slot_count = gatekeeper - .add_update_appointment(user_id, new_uuid, &appointment) + .add_update_appointment(user_id, uuid, &appointment) .unwrap(); - assert!(gatekeeper.registered_users.lock().unwrap()[&user_id] - .appointments - .contains_key(&new_uuid)); + // Simulate the watcher adding the appointment in the database. + gatekeeper + .dbm + .lock() + .unwrap() + .store_appointment(uuid, &appointment) + .unwrap(); + + let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots - 1); + loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // Finally, trying to add an appointment when the user has no enough slots should fail + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); gatekeeper .registered_users .lock() @@ -634,9 +641,10 @@ mod tests { .unwrap() .available_slots = 0; assert!(matches!( - gatekeeper.add_update_appointment(user_id, generate_uuid(), &appointment), + gatekeeper.add_update_appointment(user_id, uuid, &appointment), Err(NotEnoughSlots) )); + // The entry in the database should remain unchanged in this case loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); @@ -682,144 +690,175 @@ mod tests { // Initially, there are not outdated users, so querying any block height should return an empty map for i in 0..start_height { - assert_eq!(gatekeeper.get_outdated_users(i).len(), 0); + assert_eq!(gatekeeper.get_outdated_users(i), vec![]); } // Adding a user whose subscription is outdated should return an entry let user_id = get_random_user_id(); gatekeeper.add_update_user(user_id).unwrap(); - // Add also an appointment so we can check the returned data - let appointment = generate_dummy_appointment(None); - let uuid = generate_uuid(); - gatekeeper - .add_update_appointment(user_id, uuid, &appointment) - .unwrap(); - // Check that data is not yet outdated - assert_eq!(gatekeeper.get_outdated_users(start_height).len(), 0); + assert_eq!(gatekeeper.get_outdated_users(start_height), vec![]); // Add an outdated user and check again - gatekeeper.add_outdated_user(user_id, start_height, None); - let outdated_users = gatekeeper.get_outdated_users(start_height); - assert_eq!(outdated_users.len(), 1); - assert_eq!(outdated_users[&user_id], HashSet::from_iter([uuid])); + gatekeeper.add_outdated_user(user_id, start_height); + assert_eq!(gatekeeper.get_outdated_users(start_height), vec![user_id]); } #[test] - fn test_get_outdated_appointments() { - let start_height = START_HEIGHT as u32 + EXPIRY_DELTA; - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(start_height as usize)); - - // get_outdated_appointments returns a list of appointments that were outdated at a given block height, indistinguishably of their user. - - // If there are no outdated users, there cannot be outdated appointments - for i in 0..start_height { - assert_eq!(gatekeeper.get_outdated_appointments(i).len(), 0); - } - - // Adding data about different users and appointments should return a flattened list of appointments - let user1_id = get_random_user_id(); - let user2_id = get_random_user_id(); - let uuid1 = generate_uuid(); - let uuid2 = generate_uuid(); - - // Manually set the user expiry for the test - for (user_id, uuid) in [(user1_id, uuid1), (user2_id, uuid2)] { - gatekeeper.add_outdated_user(user_id, start_height, Some(Vec::from_iter([uuid]))); - } - - let outdated_appointments = gatekeeper.get_outdated_appointments(start_height); - assert_eq!(outdated_appointments.len(), 2); - assert!(outdated_appointments.contains(&uuid1)); - assert!(outdated_appointments.contains(&uuid2)); - } - - #[test] - fn test_delete_appointments_from_memory() { + fn test_delete_appointments_without_refund() { let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + let n_users = 100; + let n_apps = 10; + let mut uuids_to_delete = Vec::new(); + let mut rest = Vec::new(); + let mut trackers = Vec::new(); + let mut users_info = HashMap::new(); - // delete_appointments will remove a list of appointments from the Gatekeeper (as long as they exist) - let mut all_appointments = HashMap::new(); - let mut to_be_deleted = HashMap::new(); - let mut rest = HashMap::new(); - for i in 1..11 { + for _ in 0..n_users { let user_id = get_random_user_id(); - let uuid = generate_uuid(); - all_appointments.insert(uuid, user_id); - - if i % 2 == 0 { - to_be_deleted.insert(uuid, user_id); - } else { - rest.insert(uuid, user_id); - } - } - - // Calling the method with unknown data should work but do nothing - assert!(gatekeeper.registered_users.lock().unwrap().is_empty()); - assert!(gatekeeper - .delete_appointments_from_memory(&all_appointments) - .is_empty()); - - // If there's matching data in the gatekeeper it should be deleted - for (uuid, user_id) in to_be_deleted.iter() { - gatekeeper.add_update_user(*user_id).unwrap(); - gatekeeper - .add_update_appointment(*user_id, *uuid, &generate_dummy_appointment(None)) - .unwrap(); - } - - // Check before deleting - assert_eq!(gatekeeper.registered_users.lock().unwrap().len(), 5); - for (uuid, user_id) in to_be_deleted.iter() { - assert!(gatekeeper.registered_users.lock().unwrap()[user_id] - .appointments - .contains_key(uuid)); - - // The slot count should be decreased now too (both in memory and in the database) - assert_ne!( - gatekeeper.registered_users.lock().unwrap()[user_id].available_slots, - gatekeeper.subscription_slots - ); - assert_ne!( + gatekeeper.add_update_user(user_id).unwrap(); + for i in 0..n_apps { + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + gatekeeper + .add_update_appointment(user_id, uuid, &appointment) + .unwrap(); + // Add the appointment to the database. This is normally done by the Watcher. gatekeeper .dbm .lock() .unwrap() - .load_user(*user_id) + .store_appointment(uuid, &appointment) + .unwrap(); + if i % 2 == 0 { + uuids_to_delete.push(uuid); + } else { + rest.push(uuid); + } + // Also trigger some of these appointments as trackers. + if i % 5 == 0 { + gatekeeper + .dbm + .lock() + .unwrap() + .store_tracker( + uuid, + &get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)), + ) + .unwrap(); + trackers.push(uuid); + } + } + users_info.insert(user_id, gatekeeper.get_user_info(user_id).unwrap().0); + } + + // Delete these appointments without refunding their owners. + gatekeeper.delete_appointments(uuids_to_delete.clone(), false); + + for uuid in uuids_to_delete.clone() { + assert!(!gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + } + for uuid in rest { + assert!(gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + } + for uuid in trackers { + if uuids_to_delete.contains(&uuid) { + // The tracker should be deleted as well. + assert!(!gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + } else { + assert!(gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + } + } + + for (user_id, user_info_before_deletion) in users_info { + // Since `refund` was false, the users' slots should not have changed after deleting appointments. + let (user_info_after_deletion, _) = gatekeeper.get_user_info(user_id).unwrap(); + assert_eq!(user_info_after_deletion, user_info_before_deletion); + } + } + + #[test] + fn test_delete_appointments_with_refund() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + let n_users = 100; + let n_apps = 10; + let mut uuids_to_delete = Vec::new(); + let mut rest = Vec::new(); + let mut trackers = Vec::new(); + let mut users_remaining_slots = HashMap::new(); + + for _ in 0..n_users { + let user_id = get_random_user_id(); + gatekeeper.add_update_user(user_id).unwrap(); + let mut user_remaining_slots = + gatekeeper.get_user_info(user_id).unwrap().0.available_slots; + for i in 0..n_apps { + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + gatekeeper + .add_update_appointment(user_id, uuid, &appointment) + .unwrap(); + // Add the appointment to the database. This is normally done by the Watcher. + gatekeeper + .dbm + .lock() .unwrap() - .available_slots, - gatekeeper.subscription_slots - ); - } - for (_, user_id) in rest.iter() { - assert!(!gatekeeper - .registered_users - .lock() - .unwrap() - .contains_key(user_id)); + .store_appointment(uuid, &appointment) + .unwrap(); + if i % 2 == 0 { + // We don't reduce the remaining slots for the appointments which are + // going to delete since we will refund their owners. + uuids_to_delete.push(uuid); + } else { + rest.push(uuid); + user_remaining_slots -= compute_appointment_slots( + appointment.encrypted_blob().len(), + ENCRYPTED_BLOB_MAX_SIZE, + ); + } + // Also trigger some of these appointments as trackers. + if i % 5 == 0 { + gatekeeper + .dbm + .lock() + .unwrap() + .store_tracker( + uuid, + &get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)), + ) + .unwrap(); + trackers.push(uuid); + } + } + users_remaining_slots.insert(user_id, user_remaining_slots); } - // And after - gatekeeper.delete_appointments_from_memory(&all_appointments); - for (uuid, user_id) in to_be_deleted.iter() { - assert!(!gatekeeper.registered_users.lock().unwrap()[user_id] - .appointments - .contains_key(uuid)); + // Delete these appointments and refund their owners their slots back. + gatekeeper.delete_appointments(uuids_to_delete.clone(), true); - // The slot count is back to default + for uuid in uuids_to_delete.clone() { + assert!(!gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + } + for uuid in rest { + assert!(gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + } + for uuid in trackers { + if uuids_to_delete.contains(&uuid) { + // The tracker should be deleted as well. + assert!(!gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + } else { + assert!(gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + } + } + + for (user_id, correct_remaining_slots) in users_remaining_slots { + let remaining_slots_from_db = + gatekeeper.get_user_info(user_id).unwrap().0.available_slots; + assert_eq!(remaining_slots_from_db, correct_remaining_slots); assert_eq!( - gatekeeper.registered_users.lock().unwrap()[user_id].available_slots, - gatekeeper.subscription_slots + gatekeeper.registered_users.lock().unwrap()[&user_id].available_slots, + correct_remaining_slots ); } - for (_, user_id) in rest.iter() { - assert!(!gatekeeper - .registered_users - .lock() - .unwrap() - .contains_key(user_id)); - } } #[test] @@ -835,7 +874,7 @@ mod tests { let user3_id = get_random_user_id(); for user_id in &[user1_id, user2_id, user3_id] { - gatekeeper.add_outdated_user(*user_id, chain.tip().height + 1, None) + gatekeeper.add_outdated_user(*user_id, chain.tip().height + 1) } // Connect a new block. Outdated users are deleted diff --git a/teos/src/main.rs b/teos/src/main.rs index 1c60eed..bdd30e0 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -48,6 +48,7 @@ where { let mut last_n_blocks = Vec::with_capacity(n); for _ in 0..n { + log::debug!("Fetching block #{}", last_known_block.height); let block = poller.fetch_block(&last_known_block).await?; last_known_block = poller.look_up_previous_header(&last_known_block).await?; last_n_blocks.push(block); @@ -309,8 +310,8 @@ async fn main() { let shutdown_signal_tor = shutdown_signal_rpc_api.clone(); // The ordering here actually matters. Listeners are called by order, and we want the gatekeeper to be called - // last, so both the Watcher and the Responder can query the necessary data from it during data deletion. - let listener = &(watcher.clone(), &(responder, gatekeeper)); + // first so it updates the users' states and both the Watcher and the Responder operate only on registered users. + let listener = &(gatekeeper, &(watcher.clone(), responder)); let cache = &mut UnboundedCache::new(); let spv_client = SpvClient::new(tip, poller, cache, listener); let mut chain_monitor = ChainMonitor::new( diff --git a/teos/src/responder.rs b/teos/src/responder.rs index 3ace73e..b7412cc 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -1,7 +1,6 @@ //! Logic related to the Responder, the components in charge of making sure breaches get properly punished. -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; +use std::collections::HashSet; use std::sync::{Arc, Mutex}; use bitcoin::{consensus, BlockHash}; @@ -16,7 +15,7 @@ use teos_common::UserId; use crate::carrier::Carrier; use crate::dbm::DBM; use crate::extended_appointment::UUID; -use crate::gatekeeper::{Gatekeeper, UserInfo}; +use crate::gatekeeper::Gatekeeper; use crate::tx_index::TxIndex; use crate::watcher::Breach; @@ -30,14 +29,6 @@ pub enum ConfirmationStatus { InMempoolSince(u32), IrrevocablyResolved, Rejected(i32), - ReorgedOut, -} - -/// Reason why the tracker is deleted. Used for logging purposes. -enum DeletionReason { - Outdated, - Rejected, - Completed, } impl ConfirmationStatus { @@ -72,27 +63,6 @@ impl ConfirmationStatus { } } -/// Minimal data required in memory to keep track of transaction trackers. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct TrackerSummary { - /// Identifier of the user who arranged the appointment. - user_id: UserId, - /// Transaction id the [Responder] is keeping track of. - penalty_txid: Txid, - /// The confirmation status of a given tracker. - status: ConfirmationStatus, -} - -impl TrackerSummary { - pub fn new(user_id: UserId, penalty_txid: Txid, status: ConfirmationStatus) -> Self { - Self { - user_id, - penalty_txid, - status, - } - } -} - /// Structure to keep track of triggered appointments. /// /// It is analogous to [ExtendedAppointment](crate::extended_appointment::ExtendedAppointment) for the [`Watcher`](crate::watcher::Watcher). @@ -118,15 +88,6 @@ impl TransactionTracker { user_id, } } - - /// Computes the [TrackerSummary] of the [TransactionTracker]. - pub fn get_summary(&self) -> TrackerSummary { - TrackerSummary { - user_id: self.user_id, - penalty_txid: self.penalty_tx.txid(), - status: self.status, - } - } } impl From for common_msgs::Tracker { @@ -139,6 +100,22 @@ impl From for common_msgs::Tracker { } } +/// A struct that packages the summary of a tracker's penalty transaction. +#[derive(Debug, PartialEq)] +pub(crate) struct PenaltySummary { + pub penalty_txid: Txid, + pub status: ConfirmationStatus, +} + +impl PenaltySummary { + pub fn new(penalty_txid: Txid, status: ConfirmationStatus) -> Self { + PenaltySummary { + penalty_txid, + status, + } + } +} + /// Component in charge of keeping track of triggered appointments. /// /// The [Responder] receives data from the [Watcher](crate::watcher::Watcher) in form of a [Breach]. @@ -146,11 +123,6 @@ impl From for common_msgs::Tracker { /// The [Transaction] is then monitored to make sure it makes it to a block and it gets [irrevocably resolved](https://github.com/lightning/bolts/blob/master/05-onchain.md#general-nomenclature). #[derive(Debug)] pub struct Responder { - /// A map holding a summary of every tracker ([TransactionTracker]) hold by the [Responder], identified by [UUID]. - /// The identifiers match those used by the [Watcher](crate::watcher::Watcher). - trackers: Mutex>, - /// A map between [Txid]s and [UUID]s. - tx_tracker_map: Mutex>>, /// A local, pruned, [TxIndex] used to avoid the need of `txindex=1`. tx_index: Mutex>, /// A [Carrier] instance. Data is sent to the `bitcoind` through it. @@ -159,6 +131,8 @@ pub struct Responder { gatekeeper: Arc, /// A [DBM] (database manager) instance. Used to persist tracker data into disk. dbm: Arc>, + /// A list of all the reorged trackers that might need to be republished after reorg resolution. + reorged_trackers: Mutex>, } impl Responder { @@ -170,36 +144,28 @@ impl Responder { gatekeeper: Arc, dbm: Arc>, ) -> Self { - let mut trackers = HashMap::new(); - let mut tx_tracker_map: HashMap> = HashMap::new(); - - for (uuid, summary) in dbm.lock().unwrap().load_tracker_summaries() { - if let Some(map) = tx_tracker_map.get_mut(&summary.penalty_txid) { - map.insert(uuid); - } else { - tx_tracker_map.insert(summary.penalty_txid, HashSet::from_iter(vec![uuid])); - } - trackers.insert(uuid, summary); - } - Responder { carrier: Mutex::new(carrier), - trackers: Mutex::new(trackers), - tx_tracker_map: Mutex::new(tx_tracker_map), tx_index: Mutex::new(TxIndex::new(last_n_blocs, last_known_block_height)), dbm, gatekeeper, + reorged_trackers: Mutex::new(HashSet::new()), } } /// Returns whether the [Responder] has been created from scratch (fresh) or from backed-up data. pub fn is_fresh(&self) -> bool { - self.trackers.lock().unwrap().is_empty() + self.get_trackers_count() == 0 } - /// Gets the total number of trackers in the responder. + /// Gets the total number of trackers in the [Responder]. pub(crate) fn get_trackers_count(&self) -> usize { - self.trackers.lock().unwrap().len() + self.dbm.lock().unwrap().get_trackers_count() + } + + /// Checks whether the [Responder] has gone through a reorg and some transactions should to be resent. + fn coming_from_reorg(&self) -> bool { + !self.reorged_trackers.lock().unwrap().is_empty() } /// Data entry point for the [Responder]. Handles a [Breach] provided by the [Watcher](crate::watcher::Watcher). @@ -212,21 +178,15 @@ impl Responder { breach: Breach, user_id: UserId, ) -> ConfirmationStatus { - // Do not add already added trackers. This can only happen if handle_breach is called twice with the same data, which can only happen - // if Watcher::block_connected is interrupted during execution and called back during bootstrap. - if let Some(tracker) = self.trackers.lock().unwrap().get(&uuid) { - return tracker.status; - } - let mut carrier = self.carrier.lock().unwrap(); let tx_index = self.tx_index.lock().unwrap(); // Check whether the transaction is in mempool or part of our internal txindex. Send it to our node otherwise. - let status = if carrier.in_mempool(&breach.penalty_tx.txid()) { + let status = if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { + ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) + } else if carrier.in_mempool(&breach.penalty_tx.txid()) { // If it's in mempool we assume it was just included ConfirmationStatus::InMempoolSince(carrier.block_height()) - } else if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { - ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) } else { carrier.send_transaction(&breach.penalty_tx) }; @@ -253,271 +213,177 @@ impl Responder { user_id: UserId, status: ConfirmationStatus, ) { - let tracker = TransactionTracker::new(breach, user_id, status); - - self.trackers + if self + .dbm .lock() .unwrap() - .insert(uuid, tracker.get_summary()); - - let mut tx_tracker_map = self.tx_tracker_map.lock().unwrap(); - if let Some(map) = tx_tracker_map.get_mut(&tracker.penalty_tx.txid()) { - map.insert(uuid); + .store_tracker(uuid, &TransactionTracker::new(breach, user_id, status)) + .is_ok() + { + log::info!("New tracker added (uuid={uuid})"); } else { - tx_tracker_map.insert(tracker.penalty_tx.txid(), HashSet::from_iter(vec![uuid])); + log::error!( + "Failed to store tracker in database (uuid={uuid}). It might be already stored." + ); } - - self.dbm - .lock() - .unwrap() - .store_tracker(uuid, &tracker) - .unwrap(); - log::info!("New tracker added (uuid={uuid})"); } /// Checks whether a given tracker can be found in the [Responder]. pub(crate) fn has_tracker(&self, uuid: UUID) -> bool { - // has_tracker should return true as long as the given tracker is hold by the Responder. - // If the tracker is partially kept, the function will log and the return will be false. - // This may point out that some partial data deletion is happening, which must be fixed. - self.trackers - .lock() - .unwrap() - .get(&uuid) - .map_or(false, |tracker| { - self.tx_tracker_map - .lock() - .unwrap() - .get(&tracker.penalty_txid) - .map_or( - { - log::debug!( - "Partially found Tracker. Some data may have not been properly deleted" - ); - false - }, - |_| true, - ) - }) - } - - /// Gets a tracker from the [Responder] if found. [None] otherwise. - /// - /// The [TransactionTracker] is queried to the [DBM]. - pub(crate) fn get_tracker(&self, uuid: UUID) -> Option { - if self.trackers.lock().unwrap().contains_key(&uuid) { - self.dbm.lock().unwrap().load_tracker(uuid) - } else { - None - } + self.dbm.lock().unwrap().tracker_exists(uuid) } /// Checks the confirmation count for the [TransactionTracker]s. /// /// For unconfirmed transactions, it checks whether they have been confirmed or keep missing confirmations. /// For confirmed transactions, nothing is done until they are completed (confirmation count reaches [IRREVOCABLY_RESOLVED](constants::IRREVOCABLY_RESOLVED)) - /// Returns the set of completed trackers. - fn check_confirmations(&self, txids: &[Txid], current_height: u32) -> HashSet { - let mut completed_trackers = HashSet::new(); + /// Returns the set of completed trackers or [None] if none were completed. + fn check_confirmations(&self, txids: HashSet, current_height: u32) -> Option> { + let mut completed_trackers = Vec::new(); + let mut reorged_trackers = self.reorged_trackers.lock().unwrap(); + let dbm = self.dbm.lock().unwrap(); - for (uuid, tracker) in self.trackers.lock().unwrap().iter_mut() { - if let ConfirmationStatus::ConfirmedIn(h) = tracker.status { + for (uuid, penalty_summary) in dbm.load_penalties_summaries() { + if txids.contains(&penalty_summary.penalty_txid) { + // First confirmation was received + dbm.update_tracker_status(uuid, &ConfirmationStatus::ConfirmedIn(current_height)) + .unwrap(); + // Remove that uuid from reorged trackers if it was confirmed. + reorged_trackers.remove(&uuid); + // TODO: We won't need this check when we persist the correct tracker status + // in the DB after migrations are supported. + } else if reorged_trackers.contains(&uuid) { + // Don't consider reorged trackers since they have wrong DB status. + continue; + } else if let ConfirmationStatus::ConfirmedIn(h) = penalty_summary.status { let confirmations = current_height - h; if confirmations == constants::IRREVOCABLY_RESOLVED { // Tracker is deep enough in the chain, it can be deleted - completed_trackers.insert(*uuid); + completed_trackers.push(uuid); } else { log::info!("{uuid} received a confirmation (count={confirmations})"); } - } else if txids.contains(&tracker.penalty_txid) { - // First confirmation was received - tracker.status = ConfirmationStatus::ConfirmedIn(current_height); - } else if let ConfirmationStatus::InMempoolSince(h) = tracker.status { + } else if let ConfirmationStatus::InMempoolSince(h) = penalty_summary.status { // Log all transactions that have missed confirmations log::info!( "Transaction missed a confirmation: {} (missed conf count: {})", - tracker.penalty_txid, + penalty_summary.penalty_txid, current_height - h ); } } - completed_trackers + (!completed_trackers.is_empty()).then_some(completed_trackers) } - /// Gets a map of transactions that need to be rebroadcast. A [Transaction] is flagged to be rebroadcast - /// if its missed confirmation count has reached the threshold ([CONFIRMATIONS_BEFORE_RETRY]) or if they have been - /// reorged out of the chain. If the transaction has been reorged out, the commitment transaction is also returned. + /// Handles the reorged out trackers when we start connecting to the stronger chain. /// - /// Given the [Responder] only keeps around the minimal data to track transactions, the [TransactionTracker]s - /// are queried to the [DBM]. - fn get_txs_to_rebroadcast( - &self, - height: u32, - ) -> HashMap)> { - let dbm = self.dbm.lock().unwrap(); - let mut tx_to_rebroadcast = HashMap::new(); - let mut tracker: TransactionTracker; - - for (uuid, t) in self.trackers.lock().unwrap().iter() { - if let ConfirmationStatus::InMempoolSince(h) = t.status { - if (height - h) as u8 >= CONFIRMATIONS_BEFORE_RETRY { - tracker = dbm.load_tracker(*uuid).unwrap(); - tx_to_rebroadcast.insert(*uuid, (tracker.penalty_tx, None)); - } - } else if let ConfirmationStatus::ReorgedOut = t.status { - tracker = dbm.load_tracker(*uuid).unwrap(); - tx_to_rebroadcast.insert(*uuid, (tracker.penalty_tx, Some(tracker.dispute_tx))); - } - } - - tx_to_rebroadcast - } - - /// Gets a collection of trackers that have been outdated. An outdated tracker is a [TransactionTracker] - /// from a user who's subscription has been outdated (and therefore will be removed from the tower). + /// This is called in the first block connection after a bunch of block disconnections. + /// It tries to publish the dispute and penalty transactions of reorged trackers to the blockchain. /// - /// Trackers are only returned as long as they have not been confirmed, otherwise we'll keep watching for then anyway. - fn get_outdated_trackers(&self, block_height: u32) -> HashSet { - let mut outdated_trackers = HashSet::new(); - let trackers = self.trackers.lock().unwrap(); - for uuid in self - .gatekeeper - .get_outdated_appointments(block_height) - .intersection(&trackers.keys().cloned().collect()) - { - if let ConfirmationStatus::InMempoolSince(_) = trackers[uuid].status { - outdated_trackers.insert(*uuid); - } - } - - outdated_trackers - } - - /// Rebroadcasts a list of penalty transactions that have missed too many confirmations (or that have been reorged out). - /// - /// This covers both the case where a transaction is not getting confirmations (most likely due to low fess, and needs to be bumped), - /// and the case where the transaction has been reorged out of the chain. For the former, there's no much to be done at the moment (until anchors), - /// for the latter, we need to rebroadcast the penalty (and potentially the commitment if that has also been reorged). - /// - /// Given how the confirmation status and reorgs work with a bitcoind backend, we will be rebroadcasting this during the first new connected block - /// after a reorg, but bitcoind will already be at the new tip. If the transaction is accepted, we won't do anything else until passed the new tip, - /// otherwise, we could potentially try to rebroadcast again while processing the upcoming reorged blocks (if the tx hits [CONFIRMATIONS_BEFORE_RETRY]). - /// - /// Returns a tuple with two maps, one containing the trackers that where successfully rebroadcast and another one containing the ones that were rejected. - fn rebroadcast( - &self, - txs: HashMap)>, - ) -> (HashMap, HashSet) { - let mut accepted = HashMap::new(); - let mut rejected = HashSet::new(); - - let mut trackers = self.trackers.lock().unwrap(); + /// Returns a vector of rejected trackers during rebroadcast if any were rejected, [None] otherwise. + fn handle_reorged_txs(&self, height: u32) -> Option> { + // NOTE: We are draining the reorged trackers set, meaning that we won't try sending these disputes again. + let reorged_trackers: Vec = self.reorged_trackers.lock().unwrap().drain().collect(); let mut carrier = self.carrier.lock().unwrap(); - let tx_index = self.tx_index.lock().unwrap(); + let dbm = self.dbm.lock().unwrap(); - for (uuid, (penalty_tx, dispute_tx)) in txs.into_iter() { - let status = if let Some(dispute_tx) = dispute_tx { - // The tracker was reorged out, and the dispute may potentially not be in the chain (or mempool) anymore. - if tx_index.contains_key(&dispute_tx.txid()) - | carrier.in_mempool(&dispute_tx.txid()) - { - // Dispute tx is on chain (or mempool), so we only need to care about the penalty - carrier.send_transaction(&penalty_tx) - } else { - // Dispute tx has also been reorged out, meaning that both transactions need to be broadcast. - // DISCUSS: For lightning transactions, if the dispute has been reorged the penalty cannot make it to the network. - // If we keep this general, the dispute can simply be a trigger and the penalty doesn't necessarily have to spend from it. - // We'll keel it lightning specific, at least for now. - let status = carrier.send_transaction(&dispute_tx); - if let ConfirmationStatus::Rejected(e) = status { - log::error!( - "Reorged dispute transaction rejected during rebroadcast: {} (reason: {e})", - dispute_tx.txid() + let mut rejected = Vec::new(); + // Republish all the dispute transactions of the reorged trackers. + for uuid in reorged_trackers { + let tracker = dbm.load_tracker(uuid).unwrap(); + let dispute_txid = tracker.dispute_tx.txid(); + // Try to publish the dispute transaction. + let should_publish_penalty = match carrier.send_transaction(&tracker.dispute_tx) { + ConfirmationStatus::InMempoolSince(_) => { + log::info!( + "Reorged dispute tx (txid={}) is in the mempool now", + dispute_txid ); - status - } else { - // The dispute was accepted, so we can rebroadcast the penalty. - carrier.send_transaction(&penalty_tx) - } + true } - } else { - // The tracker has simply reached CONFIRMATIONS_BEFORE_RETRY missed confirmations. - log::warn!( - "Penalty transaction has missed many confirmations: {}", - penalty_tx.txid() - ); - carrier.send_transaction(&penalty_tx) + // NOTE: We aren't fully synced with the bitcoind backend so can't check if the dispute tx is in our txindex. + ConfirmationStatus::IrrevocablyResolved => { + log::info!( + "Reorged dispute tx (txid={}) is already on the strong chain", + dispute_txid + ); + true + } + ConfirmationStatus::Rejected(e) => { + log::error!( + "Reorged dispute tx (txid={}) rejected during rebroadcast (reason: {e:?})", + dispute_txid + ); + false + } + x => unreachable!( + "`Carrier::send_transaction` shouldn't return this variant: {:?}", + x + ), }; - if let ConfirmationStatus::Rejected(_) = status { - rejected.insert(uuid); + if should_publish_penalty { + // Try to rebroadcast the penalty tx. + if let ConfirmationStatus::Rejected(_) = + carrier.send_transaction(&tracker.penalty_tx) + { + rejected.push(uuid) + } else { + // The penalty might actually be confirmed (ConfirmationStatus::IrrevocablyResolved) since bitcoind + // is fully synced with the stronger chain already, but we won't know which block was it confirmed in. + // We should see the tracker appear in the blockchain in the next couple of connected blocks. + dbm.update_tracker_status(uuid, &ConfirmationStatus::InMempoolSince(height)) + .unwrap() + } } else { - // Update the tracker if it gets accepted. This will also update the height (since when we are counting the tracker - // to have been in mempool), so it resets the wait period instead of trying to rebroadcast every block. - // DISCUSS: We may want to find another approach in the future for the InMempoool transactions. - trackers.get_mut(&uuid).unwrap().status = status; - accepted.insert(uuid, status); + rejected.push(uuid) } } - (accepted, rejected) + (!rejected.is_empty()).then_some(rejected) } - // DISCUSS: Check comment regarding callbacks in watcher.rs - - /// Deletes trackers from memory. + /// Rebroadcasts a list of penalty transactions that have missed too many confirmations. /// - /// Logs a different message depending on whether the trackers have been outdated or completed. - fn delete_trackers_from_memory(&self, uuids: &HashSet, reason: DeletionReason) { - let mut trackers = self.trackers.lock().unwrap(); - let mut tx_tracker_map = self.tx_tracker_map.lock().unwrap(); - for uuid in uuids.iter() { - match reason { - DeletionReason::Completed => log::info!("Appointment completed. Penalty transaction was irrevocably confirmed: {uuid}"), - DeletionReason::Outdated => log::info!("Appointment couldn't be completed. Expiry reached but penalty didn't make it to the chain: {uuid}"), - DeletionReason::Rejected => log::info!("Appointment couldn't be completed. Either the dispute or the penalty txs where rejected during rebroadcast: {uuid}"), - } + /// This covers the case where a transaction is not getting confirmations (most likely due to low + /// fess and needs to be bumped, but there is not much we can do until anchors). + /// + /// Returns a vector of rejected trackers during rebroadcast if any were rejected, [None] otherwise. + fn rebroadcast_stale_txs(&self, height: u32) -> Option> { + let dbm = self.dbm.lock().unwrap(); + let mut carrier = self.carrier.lock().unwrap(); + let mut rejected = Vec::new(); - match trackers.remove(uuid) { - Some(tracker) => { - let trackers = tx_tracker_map.get_mut(&tracker.penalty_txid).unwrap(); - - if trackers.len() == 1 { - tx_tracker_map.remove(&tracker.penalty_txid); - - log::info!( - "No more trackers for penalty transaction: {}", - tracker.penalty_txid - ); - } else { - trackers.remove(uuid); - } - } - None => { - // This should never happen. Logging just in case so we can fix it if so - log::error!("Completed tracker not found when cleaning: {uuid}"); - } + // Retry sending trackers which have been in the mempool since more than `CONFIRMATIONS_BEFORE_RETRY` blocks. + let stale_confirmation_status = + ConfirmationStatus::InMempoolSince(height - CONFIRMATIONS_BEFORE_RETRY as u32); + // NOTE: Ideally this will only pull UUIDs which have been in mempool since `CONFIRMATIONS_BEFORE_RETRY`, but + // might also return ones which have been there for a longer period. This can only happen if the tower missed + // a couple of block connections due to a force update. + for uuid in dbm + .load_trackers_with_confirmation_status(stale_confirmation_status) + .unwrap() + { + let tracker = dbm.load_tracker(uuid).unwrap(); + log::warn!( + "Penalty transaction has missed many confirmations: {}", + tracker.penalty_tx.txid() + ); + // Rebroadcast the penalty transaction. + let status = carrier.send_transaction(&tracker.penalty_tx); + if let ConfirmationStatus::Rejected(_) = status { + rejected.push(uuid); + } else { + // DISCUSS: What if the tower was down for some time and was later force updated while this penalty got on-chain? + // Sending it will yield `ConfirmationStatus::IrrevocablyResolved` which would panic here. + // We might want to replace `ConfirmationStatus::IrrevocablyResolved` variant with + // `ConfirmationStatus::ConfirmedIn(height - IRREVOCABLY_RESOLVED) + dbm.update_tracker_status(uuid, &status).unwrap(); } } - } - /// Deletes trackers from memory and the database. - /// - /// Removes all data related to the appointment from the database in cascade. - fn delete_trackers( - &self, - uuids: &HashSet, - updated_users: &HashMap, - reason: DeletionReason, - ) { - if !uuids.is_empty() { - self.delete_trackers_from_memory(uuids, reason); - self.dbm - .lock() - .unwrap() - .batch_remove_appointments(uuids, updated_users); - } + (!rejected.is_empty()).then_some(rejected) } } @@ -548,69 +414,55 @@ impl chain::Listen for Responder { .collect(); self.tx_index.lock().unwrap().update(*header, &txs); - if !self.trackers.lock().unwrap().is_empty() { - // Complete those appointments that are due at this height - let completed_trackers = self.check_confirmations( - &txdata.iter().map(|(_, tx)| tx.txid()).collect::>(), - height, - ); - let trackers_to_delete_gk = completed_trackers - .iter() - .map(|uuid| (*uuid, self.trackers.lock().unwrap()[uuid].user_id)) - .collect(); - self.delete_trackers( - &completed_trackers, - &self - .gatekeeper - .delete_appointments_from_memory(&trackers_to_delete_gk), - DeletionReason::Completed, - ); + // Delete trackers completed at this height + if let Some(trackers) = self.check_confirmations(txs.keys().cloned().collect(), height) { + self.gatekeeper.delete_appointments(trackers, true); + } - // Also delete trackers from outdated users (from memory only, the db deletion is handled by the Gatekeeper) - self.delete_trackers_from_memory( - &self.get_outdated_trackers(height), - DeletionReason::Outdated, - ); - - // Rebroadcast those transactions that need to - let (_, rejected_trackers) = self.rebroadcast(self.get_txs_to_rebroadcast(height)); - // Delete trackers rejected during rebroadcast - let trackers_to_delete_gk = rejected_trackers - .iter() - .map(|uuid| (*uuid, self.trackers.lock().unwrap()[uuid].user_id)) - .collect(); - self.delete_trackers( - &rejected_trackers, - &self - .gatekeeper - .delete_appointments_from_memory(&trackers_to_delete_gk), - DeletionReason::Rejected, - ); - - // Remove all receipts created in this block - self.carrier.lock().unwrap().clear_receipts(); - - if self.trackers.lock().unwrap().is_empty() { - log::info!("No more pending trackers"); + let mut trackers_to_delete = Vec::new(); + // We might be connecting a new block after a disconnection (reorg). + // We will need to update those trackers that have been reorged. + if self.coming_from_reorg() { + // Handle reorged transactions. This clears `self.reorged_trackers`. + if let Some(trackers) = self.handle_reorged_txs(height) { + trackers_to_delete.extend(trackers); } } + + // Rebroadcast those transactions that need to + if let Some(trackers) = self.rebroadcast_stale_txs(height) { + trackers_to_delete.extend(trackers); + } + + if !trackers_to_delete.is_empty() { + self.gatekeeper + .delete_appointments(trackers_to_delete, false); + } + + // Remove all receipts created in this block + self.carrier.lock().unwrap().clear_receipts(); } /// Handles reorgs in the [Responder]. fn block_disconnected(&self, header: &BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); + // Update the carrier and our tx_index. self.carrier.lock().unwrap().update_height(height); self.tx_index .lock() .unwrap() .remove_disconnected_block(&header.block_hash()); - - for tracker in self.trackers.lock().unwrap().values_mut() { - // The transaction has been unconfirmed. Flag it as reorged out so we can rebroadcast it. - if tracker.status == ConfirmationStatus::ConfirmedIn(height) { - tracker.status = ConfirmationStatus::ReorgedOut; - } - } + // And store the reorged transactions to be retried later. + // TODO: Not only confirmed trackers need to be marked as reorged, but trackers that hasn't confirmed but their + // dispute did confirm in the reorged block. We can pull dispute txids of non confirmed penalties and get their + // confirmation block from our tx_index. + self.reorged_trackers.lock().unwrap().extend( + self.dbm + .lock() + .unwrap() + .load_trackers_with_confirmation_status(ConfirmationStatus::ConfirmedIn(height)) + .unwrap(), + ); } } @@ -618,74 +470,82 @@ impl chain::Listen for Responder { mod tests { use super::*; use lightning::chain::Listen; + use teos_common::appointment::Locator; + use std::collections::HashMap; + use std::iter::FromIterator; use std::sync::{Arc, Mutex}; use crate::dbm::DBM; - use crate::gatekeeper::UserInfo; use crate::rpc_errors; use crate::test_utils::{ - create_carrier, generate_dummy_appointment_with_user, generate_uuid, get_last_n_blocks, - get_random_breach, get_random_tracker, get_random_tx, store_appointment_and_fks_to_db, - BitcoindStopper, Blockchain, MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, - SLOTS, START_HEIGHT, SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + create_carrier, generate_dummy_appointment, generate_dummy_appointment_with_user, + generate_uuid, get_last_n_blocks, get_random_breach, get_random_tracker, get_random_tx, + store_appointment_and_its_user, BitcoindStopper, Blockchain, MockedServerQuery, DURATION, + EXPIRY_DELTA, SLOTS, START_HEIGHT, }; use teos_common::constants::IRREVOCABLY_RESOLVED; use teos_common::test_utils::get_random_user_id; + impl TransactionTracker { + pub fn locator(&self) -> Locator { + Locator::new(self.dispute_tx.txid()) + } + + pub fn uuid(&self) -> UUID { + UUID::new(self.locator(), self.user_id) + } + } + impl PartialEq for Responder { fn eq(&self, other: &Self) -> bool { - *self.trackers.lock().unwrap() == *other.trackers.lock().unwrap() - && *self.tx_tracker_map.lock().unwrap() == *other.tx_tracker_map.lock().unwrap() + // Same in-memory data. + *self.reorged_trackers.lock().unwrap() == *other.reorged_trackers.lock().unwrap() && + *self.tx_index.lock().unwrap() == *other.tx_index.lock().unwrap() && + // && Same DB data. + self.get_trackers() == other.get_trackers() } } impl Eq for Responder {} impl Responder { - pub(crate) fn get_trackers(&self) -> &Mutex> { - &self.trackers + pub(crate) fn get_trackers(&self) -> HashMap { + self.dbm.lock().unwrap().load_trackers(None) } pub(crate) fn get_carrier(&self) -> &Mutex { &self.carrier } - pub(crate) fn add_random_tracker( - &self, - uuid: UUID, - status: ConfirmationStatus, - ) -> TransactionTracker { + pub(crate) fn add_random_tracker(&self, status: ConfirmationStatus) -> TransactionTracker { let user_id = get_random_user_id(); let tracker = get_random_tracker(user_id, status); - self.add_dummy_tracker(uuid, &tracker); + self.add_dummy_tracker(&tracker); tracker } - pub(crate) fn add_dummy_tracker(&self, uuid: UUID, tracker: &TransactionTracker) { - // Add data to memory - self.trackers - .lock() - .unwrap() - .insert(uuid, tracker.get_summary()); - self.tx_tracker_map - .lock() - .unwrap() - .insert(tracker.penalty_tx.txid(), HashSet::from_iter([uuid])); - - // Add data to the db + pub(crate) fn add_dummy_tracker(&self, tracker: &TransactionTracker) { let (_, appointment) = generate_dummy_appointment_with_user( tracker.user_id, Some(&tracker.dispute_tx.txid()), ); - store_appointment_and_fks_to_db(&self.dbm.lock().unwrap(), uuid, &appointment); + store_appointment_and_its_user(&self.dbm.lock().unwrap(), &appointment); self.dbm .lock() .unwrap() - .store_tracker(uuid, tracker) + .store_tracker(appointment.uuid(), tracker) .unwrap(); } + + fn store_dummy_appointment_to_db(&self) -> (UserId, UUID) { + let appointment = generate_dummy_appointment(None); + let (uuid, user_id) = (appointment.uuid(), appointment.user_id); + // Store the appointment and the user to the DB. + store_appointment_and_its_user(&self.dbm.lock().unwrap(), &appointment); + (user_id, uuid) + } } async fn create_responder( @@ -733,23 +593,17 @@ mod tests { #[test] fn test_confirmation_status_from_db_data() { // These are pretty simple tests. The db can only store trackers with a confirmation status - // that's either ConfirmedIn or InMempoolSince (Rejected and Reorged are never passed to store). + // that's either ConfirmedIn or InMempoolSince (Rejected and IrrevocablyResolved are never passed to store). let h = 21; - let statuses = [true, false]; - for status in statuses { - if status { - assert_eq!( - ConfirmationStatus::from_db_data(h, status), - ConfirmationStatus::ConfirmedIn(h) - ); - } else { - assert_eq!( - ConfirmationStatus::from_db_data(h, status), - ConfirmationStatus::InMempoolSince(h) - ); - } - } + assert_eq!( + ConfirmationStatus::from_db_data(h, true), + ConfirmationStatus::ConfirmedIn(h) + ); + assert_eq!( + ConfirmationStatus::from_db_data(h, false), + ConfirmationStatus::InMempoolSince(h) + ); } #[test] @@ -767,7 +621,7 @@ mod tests { Some((h, false)) ); assert_eq!(ConfirmationStatus::Rejected(0).to_db_data(), None); - assert_eq!(ConfirmationStatus::ReorgedOut.to_db_data(), None); + assert_eq!(ConfirmationStatus::IrrevocablyResolved.to_db_data(), None); } #[tokio::test] @@ -783,11 +637,7 @@ mod tests { // If we add some trackers to the system and create a new Responder reusing the same db // (as if simulating a bootstrap from existing data), the data should be properly loaded. for i in 0..10 { - // Add the necessary FKs in the database - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); let s = if i % 2 == 0 { ConfirmationStatus::InMempoolSince(i) @@ -809,46 +659,31 @@ mod tests { let start_height = START_HEIGHT as u32; let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); - let penalty_txid = breach.penalty_tx.txid(); assert_eq!( responder.handle_breach(uuid, breach, user_id), ConfirmationStatus::InMempoolSince(start_height) ); - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); assert_eq!( - responder.trackers.lock().unwrap()[&uuid].status, + tracker.status, ConfirmationStatus::InMempoolSince(start_height) ); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&penalty_txid)); // Breaches won't be overwritten once passed to the Responder. If the same UUID is // passed twice, the receipt corresponding to the first breach will be handed back. let another_breach = get_random_breach(); assert_eq!( - responder.handle_breach(uuid, another_breach.clone(), user_id), + responder.handle_breach(uuid, another_breach, user_id), ConfirmationStatus::InMempoolSince(start_height) ); - - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + // Getting the tracker should return the old one. assert_eq!( - responder.trackers.lock().unwrap()[&uuid].status, - ConfirmationStatus::InMempoolSince(start_height) + tracker, + responder.dbm.lock().unwrap().load_tracker(uuid).unwrap() ); - assert!(!responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&another_breach.penalty_tx.txid())); } #[tokio::test] @@ -856,36 +691,25 @@ mod tests { let start_height = START_HEIGHT as u32; let (responder, _s) = init_responder(MockedServerQuery::InMempoool).await; - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); - let penalty_txid = breach.penalty_tx.txid(); assert_eq!( responder.handle_breach(uuid, breach, user_id), ConfirmationStatus::InMempoolSince(start_height) ); - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); assert_eq!( - responder.trackers.lock().unwrap()[&uuid].status, + tracker.status, ConfirmationStatus::InMempoolSince(start_height) ); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&penalty_txid)); } #[tokio::test] async fn test_handle_breach_accepted_in_txindex() { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); let penalty_txid = breach.penalty_tx.txid(); @@ -909,16 +733,11 @@ mod tests { responder.handle_breach(uuid, breach, user_id), ConfirmationStatus::ConfirmedIn(target_height) ); - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); + let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); assert_eq!( - responder.trackers.lock().unwrap()[&uuid].status, + tracker.status, ConfirmationStatus::ConfirmedIn(target_height) ); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&penalty_txid)); } #[tokio::test] @@ -931,18 +750,12 @@ mod tests { let user_id = get_random_user_id(); let uuid = generate_uuid(); let breach = get_random_breach(); - let penalty_txid = breach.penalty_tx.txid(); assert_eq!( responder.handle_breach(uuid, breach, user_id), ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_ERROR) ); - assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(!responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&penalty_txid)); + assert!(!responder.has_tracker(uuid)); } #[tokio::test] @@ -950,11 +763,7 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let start_height = START_HEIGHT as u32; - // Add the necessary FKs in the database - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let mut breach = get_random_breach(); responder.add_tracker( uuid, @@ -963,21 +772,7 @@ mod tests { ConfirmationStatus::InMempoolSince(start_height), ); - // Check that the data has been added to trackers and to the tx_tracker_map - assert_eq!( - responder.trackers.lock().unwrap().get(&uuid), - Some(&TrackerSummary { - user_id, - penalty_txid: breach.penalty_tx.txid(), - status: ConfirmationStatus::InMempoolSince(start_height) - }) - ); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&breach.penalty_tx.txid())); - // Check that the data is also in the database + // Check that the data has been added to the responder. assert_eq!( responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), TransactionTracker::new( @@ -988,16 +783,9 @@ mod tests { ); // Adding a confirmed tracker should result in the same but with the height being set. - let uuid = generate_uuid(); + + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); breach = get_random_breach(); - - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - responder.add_tracker( uuid, breach.clone(), @@ -1005,23 +793,6 @@ mod tests { ConfirmationStatus::ConfirmedIn(start_height - 1), ); - assert_eq!( - responder.trackers.lock().unwrap().get(&uuid), - Some(&TrackerSummary { - user_id, - penalty_txid: breach.penalty_tx.txid(), - status: ConfirmationStatus::ConfirmedIn(start_height - 1) - }) - ); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&breach.penalty_tx.txid())); - assert_eq!( - responder.tx_tracker_map.lock().unwrap()[&breach.penalty_tx.txid()].len(), - 1 - ); assert_eq!( responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), TransactionTracker::new( @@ -1031,15 +802,8 @@ mod tests { ) ); - // Adding another breach with the same penalty transaction (but different uuid) adds an additional uuid to the map entry - let uuid = generate_uuid(); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - + // Adding another breach with the same penalty transaction (but different uuid) + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); responder.add_tracker( uuid, breach.clone(), @@ -1047,16 +811,6 @@ mod tests { ConfirmationStatus::ConfirmedIn(start_height), ); - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&breach.penalty_tx.txid())); - assert_eq!( - responder.tx_tracker_map.lock().unwrap()[&breach.penalty_tx.txid()].len(), - 2 - ); assert_eq!( responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), TransactionTracker::new( @@ -1075,10 +829,7 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Add a new tracker - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); responder.add_tracker( uuid, @@ -1089,12 +840,8 @@ mod tests { assert!(responder.has_tracker(uuid)); - // Delete the tracker and check again (updated users are irrelevant here) - responder.delete_trackers( - &HashSet::from_iter([uuid]), - &HashMap::new(), - DeletionReason::Completed, - ); + // Delete the tracker and check again. + responder.gatekeeper.delete_appointments(vec![uuid], false); assert!(!responder.has_tracker(uuid)); } @@ -1105,12 +852,10 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Store the user and the appointment in the database so we can add the tracker later on (due to FK restrictions) - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); // Data should not be there before adding it - assert_eq!(responder.get_tracker(uuid), None); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); // Data should be there now let breach = get_random_breach(); @@ -1121,7 +866,7 @@ mod tests { ConfirmationStatus::InMempoolSince(start_height), ); assert_eq!( - responder.get_tracker(uuid).unwrap(), + responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), TransactionTracker::new( breach, user_id, @@ -1129,13 +874,9 @@ mod tests { ) ); - // After deleting the data it should be gone (updated users are irrelevant here) - responder.delete_trackers( - &HashSet::from_iter([uuid]), - &HashMap::new(), - DeletionReason::Outdated, - ); - assert_eq!(responder.get_tracker(uuid), None); + // After deleting the data it should be gone + responder.gatekeeper.delete_appointments(vec![uuid], false); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); } #[tokio::test] @@ -1148,67 +889,69 @@ mod tests { let mut just_confirmed = HashSet::new(); let mut confirmed = HashSet::new(); let mut completed = HashSet::new(); - let mut txids = Vec::new(); + let mut txids = HashSet::new(); for i in 0..40 { - let user_id = get_random_user_id(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); - store_appointment_and_fks_to_db(&responder.dbm.lock().unwrap(), uuid, &appointment); - - if i % 4 == 0 { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(21), - ); - in_mempool.insert(uuid); - } else if i % 4 == 1 { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(i), - ); - just_confirmed.insert(uuid); - txids.push(breach.penalty_tx.txid()); - } else if i % 4 == 2 { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(42), - ); - confirmed.insert(uuid); - } else { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn( - target_height - constants::IRREVOCABLY_RESOLVED, - ), - ); - completed.insert(uuid); + match i % 4 { + 0 => { + responder.add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(21), + ); + in_mempool.insert(uuid); + } + 1 => { + responder.add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(i), + ); + just_confirmed.insert(uuid); + txids.insert(breach.penalty_tx.txid()); + } + 2 => { + responder.add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn(42), + ); + confirmed.insert(uuid); + } + _ => { + responder.add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn( + target_height - constants::IRREVOCABLY_RESOLVED, + ), + ); + completed.insert(uuid); + } } } // The trackers that were completed should be returned assert_eq!( completed, - responder.check_confirmations(&txids, target_height) + HashSet::from_iter(responder.check_confirmations(txids, target_height).unwrap()) ); // The ones in mempool should still be there (at the same height) for uuid in in_mempool { assert_eq!( responder - .trackers + .dbm .lock() .unwrap() - .get(&uuid) + .load_tracker(uuid) .unwrap() .status, ConfirmationStatus::InMempoolSince(21) @@ -1219,10 +962,10 @@ mod tests { for uuid in just_confirmed { assert_eq!( responder - .trackers + .dbm .lock() .unwrap() - .get(&uuid) + .load_tracker(uuid) .unwrap() .status, ConfirmationStatus::ConfirmedIn(target_height) @@ -1233,10 +976,10 @@ mod tests { for uuid in confirmed { assert_eq!( responder - .trackers + .dbm .lock() .unwrap() - .get(&uuid) + .load_tracker(uuid) .unwrap() .status, ConfirmationStatus::ConfirmedIn(42) @@ -1245,478 +988,165 @@ mod tests { } #[tokio::test] - async fn test_get_txs_to_rebroadcast() { - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let current_height = 100; - - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Transactions are flagged to be rebroadcast when they've been in mempool for longer than CONFIRMATIONS_BEFORE_RETRY - let mut txs = HashMap::new(); - - for i in 0..CONFIRMATIONS_BEFORE_RETRY + 2 { - // Add the appointment to the db so FK rules are satisfied - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - // Create a breach and add it, setting all them as unconfirmed (at different heights) - let breach = get_random_breach(); - - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(current_height - i as u32), - ); - - if i >= CONFIRMATIONS_BEFORE_RETRY { - txs.insert(uuid, (breach.penalty_tx.clone(), None)); - } - } - - assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); - } - - #[tokio::test] - async fn test_get_txs_to_rebroadcast_reorged() { - // For reorged transactions this works a bit different, the dispute transaction will also be returned here - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let current_height = 100; - - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Transactions are flagged to be rebroadcast when they've been in mempool for longer than CONFIRMATIONS_BEFORE_RETRY - let mut txs = HashMap::new(); - - for i in 0..10 { - // Add the appointment to the db so FK rules are satisfied - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - // Create a breach and add it, setting half of them as reorged - let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(current_height), - ); - - // Since we are adding trackers using add_trackers we'll need to manually change the state of the transaction - // (reorged transactions are not passed to add_tracker, they are detected after they are already there). - // Not doing so will trigger an error in the dbm since reorged transactions are not stored in the db. - if i % 2 == 0 { - responder - .trackers - .lock() - .unwrap() - .get_mut(&uuid) - .unwrap() - .status = ConfirmationStatus::ReorgedOut; - // Here the dispute is also included - txs.insert( - uuid, - (breach.penalty_tx.clone(), Some(breach.dispute_tx.clone())), - ); - } - } - - // Since we have only added confirmed and reorged transactions, we should get back only the reorged ones. - assert_eq!(responder.get_txs_to_rebroadcast(current_height), txs); - } - - #[tokio::test] - async fn test_get_outdated_trackers() { - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - - // Outdated trackers are those whose associated subscription is outdated and have not been confirmed yet (they don't have - // a single confirmation). - - // Mock data into the GK - let target_block_height = START_HEIGHT as u32; - let user_id = get_random_user_id(); - let uuids = (0..10).map(|_| generate_uuid()).collect::>(); - responder - .gatekeeper - .add_outdated_user(user_id, target_block_height, Some(uuids.clone())); - - // Mock the data to the Responder. Add data to trackers (half of them unconfirmed) - let mut target_uuids = HashSet::new(); - for (i, uuid) in uuids.into_iter().enumerate() { - let tracker = if i % 2 == 0 { - target_uuids.insert(uuid); - get_random_tracker( - user_id, - ConfirmationStatus::InMempoolSince(target_block_height), - ) - } else { - get_random_tracker( - user_id, - ConfirmationStatus::ConfirmedIn(target_block_height), - ) - }; - - responder - .trackers - .lock() - .unwrap() - .insert(uuid, tracker.get_summary()); - } - - // Check the expected data is there - assert_eq!( - responder.get_outdated_trackers(target_block_height), - target_uuids - ); - } - - #[tokio::test] - async fn test_rebroadcast_accepted() { - // This test positive rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a - // dedicated test (against bitcoind, not mocked). - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let current_height = 100; - - // Add user to the database - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Transactions are rebroadcast once they've been in mempool for CONFIRMATIONS_BEFORE_RETRY or they've been reorged out - let mut need_rebroadcast = HashSet::new(); - - for i in 0..10 { - // Generate appointment and also add it to the DB (FK checks) - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - let breach = get_random_breach(); - - let height = if i % 2 == 0 { - current_height + 1 - CONFIRMATIONS_BEFORE_RETRY as u32 - } else { - need_rebroadcast.insert(uuid); - current_height - CONFIRMATIONS_BEFORE_RETRY as u32 - }; - - responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::InMempoolSince(height), - ); - - // Reorged txs need to be set manually - if i % 2 == 1 { - responder - .trackers - .lock() - .unwrap() - .get_mut(&uuid) - .unwrap() - .status = ConfirmationStatus::ReorgedOut; - } - } - - // Check all are accepted - let (accepted, rejected) = - responder.rebroadcast(responder.get_txs_to_rebroadcast(current_height)); - let accepted_uuids: HashSet = accepted.keys().cloned().collect(); - assert_eq!(accepted_uuids, need_rebroadcast); - assert!(rejected.is_empty()); - } - - #[tokio::test] - async fn test_rebroadcast_rejected() { - // This test negative rebroadcast cases, including reorgs. However, complex reorg logic is not tested here, it will need a - // dedicated test (against bitcoind, not mocked). - let (responder, _s) = init_responder(MockedServerQuery::Error( - rpc_errors::RPC_VERIFY_ERROR as i64, - )) - .await; - let current_height = 100; - - // Add user to the database - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Transactions are rebroadcast once they've been in mempool for CONFIRMATIONS_BEFORE_RETRY or they've been reorged out - let mut need_rebroadcast = HashSet::new(); - - for i in 0..30 { - // Generate appointment and also add it to the DB (FK checks) - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - let breach = get_random_breach(); - - let height = if i % 2 == 0 { - current_height + 1 - CONFIRMATIONS_BEFORE_RETRY as u32 - } else { - need_rebroadcast.insert(uuid); - current_height - CONFIRMATIONS_BEFORE_RETRY as u32 - }; - - responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::InMempoolSince(height), - ); - - // Reorged txs need to be set manually - if i % 2 == 1 { - responder - .trackers - .lock() - .unwrap() - .get_mut(&uuid) - .unwrap() - .status = ConfirmationStatus::ReorgedOut; - } - } - - // Check all are rejected - let (accepted, rejected) = - responder.rebroadcast(responder.get_txs_to_rebroadcast(current_height)); - assert_eq!(rejected, need_rebroadcast); - assert!(accepted.is_empty()); - } - - #[tokio::test] - async fn test_delete_trackers_from_memory() { - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - - // Add user to the database - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Add some trackers both to memory and to the database - let mut to_be_deleted = HashMap::new(); + async fn test_handle_reorged_txs() { + let (responder, _s) = init_responder(MockedServerQuery::InMempoool).await; + let mut trackers = Vec::new(); for _ in 0..10 { - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(21), - ); - to_be_deleted.insert(uuid, breach.penalty_tx.txid()); + let uuid = responder + .add_random_tracker(ConfirmationStatus::ConfirmedIn(42)) + .uuid(); + responder.reorged_trackers.lock().unwrap().insert(uuid); + trackers.push(uuid); } - // Delete and check data is not in memory (the reason does not matter for the test) - responder.delete_trackers_from_memory( - &to_be_deleted.keys().cloned().collect(), - DeletionReason::Completed, - ); + let height = 100; + assert!(responder.handle_reorged_txs(height).is_none()); + // The reorged trackers buffer should be empty after this. + assert!(responder.reorged_trackers.lock().unwrap().is_empty()); - for (uuid, txid) in to_be_deleted { - // Data is not in memory - assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(!responder.tx_tracker_map.lock().unwrap().contains_key(&txid)); - - // But it can be found in the database - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_some()); - } - } - - #[tokio::test] - async fn test_delete_trackers() { - let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - - // Add user to the database - let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); - - // Delete trackers removes data from the trackers, tx_tracker_map maps, the database. The deletion of the later is - // better check in test_filtered_block_connected. Add data to the map first. - let mut all_trackers = HashSet::new(); - let mut target_trackers = HashSet::new(); - let mut uuid_txid_map = HashMap::new(); - let mut txs_with_multiple_uuids = HashSet::new(); - let mut updated_users = HashMap::new(); - - for i in 0..10 { - // Generate appointment and also add it to the DB (FK checks) - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(42), - ); - - // Make it so some of the penalties have multiple associated trackers - if i % 3 == 0 { - let uuid2 = generate_uuid(); - responder - .tx_tracker_map - .lock() - .unwrap() - .get_mut(&breach.penalty_tx.txid()) - .unwrap() - .insert(uuid2); - txs_with_multiple_uuids.insert(breach.penalty_tx.txid()); - } - - all_trackers.insert(uuid); - uuid_txid_map.insert(uuid, breach.penalty_tx.txid()); - - // Add some trackers to be deleted - if i % 2 == 0 { - // Users will also be updated once the data is deleted. - // We can made up the numbers here just to check they are updated. - target_trackers.insert(uuid); - updated_users.insert( - appointment.user_id, - UserInfo::new( - AVAILABLE_SLOTS + i, - SUBSCRIPTION_START + i, - SUBSCRIPTION_EXPIRY + i, - ), - ); - } - } - - responder.delete_trackers(&target_trackers, &updated_users, DeletionReason::Rejected); - - // Only trackers in the target_trackers map should have been removed from - // the Responder data structures. - for uuid in all_trackers { - if target_trackers.contains(&uuid) { - assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); - let penalty_txid = &uuid_txid_map[&uuid]; - // If the penalty had more than one associated uuid, only one has been deleted - // (because that's how the test has been designed) - if txs_with_multiple_uuids.contains(penalty_txid) { - assert_eq!( - responder - .tx_tracker_map - .lock() - .unwrap() - .get(penalty_txid) - .unwrap() - .len(), - 1 - ); - } else { - // Otherwise the whole structure is removed, given it is now empty - assert!(!responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(penalty_txid)); - } - } else { - assert!(responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(responder - .tx_tracker_map - .lock() - .unwrap() - .contains_key(&uuid_txid_map[&uuid])); - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_some()); - } - } - - // The users that needed to be updated in the database have been (just checking the slot count) - for (id, info) in updated_users { + // And all the reorged trackers should have in mempool since `height` status. + for uuid in trackers { assert_eq!( responder .dbm .lock() .unwrap() - .load_user(id) + .load_tracker(uuid) .unwrap() - .available_slots, - info.available_slots - ) + .status, + ConfirmationStatus::InMempoolSince(height) + ); + } + } + + #[tokio::test] + async fn test_handle_reorged_txs_rejected() { + let (responder, _s) = init_responder(MockedServerQuery::Error( + rpc_errors::RPC_VERIFY_REJECTED as i64, + )) + .await; + let n_trackers = 10; + let mut trackers = HashSet::new(); + + for _ in 0..n_trackers { + let uuid = responder + .add_random_tracker(ConfirmationStatus::ConfirmedIn(42)) + .uuid(); + responder.reorged_trackers.lock().unwrap().insert(uuid); + trackers.insert(uuid); + } + + let height = 100; + let rejected = HashSet::from_iter(responder.handle_reorged_txs(height).unwrap()); + // All the trackers should be returned as rejected. + assert_eq!(trackers, rejected); + // The reorged trackers buffer should be empty after this. + assert!(responder.reorged_trackers.lock().unwrap().is_empty()); + + // And all the reorged trackers statuses should be untouched. + for uuid in trackers { + assert_eq!( + responder + .dbm + .lock() + .unwrap() + .load_tracker(uuid) + .unwrap() + .status, + ConfirmationStatus::ConfirmedIn(42) + ); + } + } + + #[tokio::test] + async fn test_rebroadcast_stale_txs_accepted() { + let (responder, _s) = init_responder(MockedServerQuery::InMempoool).await; + let mut statues = HashMap::new(); + let height = 100; + + for i in 0..height { + let status = if i % 4 == 0 { + ConfirmationStatus::ConfirmedIn(i) + } else { + ConfirmationStatus::InMempoolSince(i) + }; + + let uuid = responder.add_random_tracker(status).uuid(); + statues.insert(uuid, status); + } + + // There should be no rejected tx. + assert!(responder.rebroadcast_stale_txs(height).is_none()); + + for (uuid, former_status) in statues { + let status = responder + .dbm + .lock() + .unwrap() + .load_tracker(uuid) + .unwrap() + .status; + if let ConfirmationStatus::InMempoolSince(h) = former_status { + if height - h >= CONFIRMATIONS_BEFORE_RETRY as u32 { + // Transactions which stayed for more than `CONFIRMATIONS_BEFORE_RETRY` should have been rebroadcasted. + assert_eq!(status, ConfirmationStatus::InMempoolSince(height)); + } else { + // Others left untouched. + assert_eq!(status, former_status); + } + } else { + // Confirmed transactions left untouched as well. + assert_eq!(status, former_status); + } + } + } + + #[tokio::test] + async fn test_rebroadcast_stale_txs_rejected() { + let (responder, _s) = init_responder(MockedServerQuery::Error( + rpc_errors::RPC_VERIFY_ERROR as i64, + )) + .await; + let mut statues = HashMap::new(); + let height = 100; + + for i in 0..height { + let status = if i % 4 == 0 { + ConfirmationStatus::ConfirmedIn(i) + } else { + ConfirmationStatus::InMempoolSince(i) + }; + + let uuid = responder.add_random_tracker(status).uuid(); + statues.insert(uuid, status); + } + + // `rebroadcast_stale_txs` will broadcast txs which has been in mempool since `CONFIRMATIONS_BEFORE_RETRY` or more + // blocks. Since our backend rejects all the txs, all these broadcasted txs should be returned from this method (rejected). + let rejected = HashSet::from_iter(responder.rebroadcast_stale_txs(height).unwrap()); + let should_reject: HashSet<_> = statues + .iter() + .filter_map(|(&uuid, &status)| { + if let ConfirmationStatus::InMempoolSince(h) = status { + (height - h >= CONFIRMATIONS_BEFORE_RETRY as u32).then_some(uuid) + } else { + None + } + }) + .collect(); + assert_eq!(should_reject, rejected); + + for (uuid, former_status) in statues { + let status = responder + .dbm + .lock() + .unwrap() + .load_tracker(uuid) + .unwrap() + .status; + // All tracker statues shouldn't change since the submitted ones were all rejected. + assert_eq!(status, former_status); } } @@ -1728,35 +1158,40 @@ mod tests { let (responder, _s) = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; - // block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder + // filtered_block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder // is keeping track of. // // If there are any trackers, the Responder will: // - Check if there is any tracker that has been completed - // - Check if there is any tracker that has been outdated // - Check if any tracker has been confirmed or add missing confirmations otherwise // - Rebroadcast all penalty transactions that need so - // - Delete completed and outdated data (including data in the GK) + // - Delete completed and invalid data (and update the data in the GK) // - Clear the Carrier issued_receipts cache + // + // We will also test that trackers for outdated users are removed by the GK. // Let's start by doing the data setup for each test (i.e. adding all the necessary data to the Responder and GK) let target_block_height = chain.get_block_count() + 1; let mut users = Vec::new(); - for _ in 2..23 { + for _ in 0..21 { let user_id = get_random_user_id(); - responder.gatekeeper.add_update_user(user_id).unwrap(); users.push(user_id); } - let mut completed_trackers = HashMap::new(); - // COMPLETED TRACKERS SETUP + let mut completed_trackers = Vec::new(); for i in 0..10 { - // Adding two trackers to each user + // Add these trackers to the first two users let user_id = users[i % 2]; - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + let dispute_tx = get_random_tx(); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + responder + .gatekeeper + .add_update_appointment(user_id, uuid, &appointment) + .unwrap(); responder .dbm .lock() @@ -1765,58 +1200,42 @@ mod tests { .unwrap(); // Trackers complete in the next block. - let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn( - target_block_height - constants::IRREVOCABLY_RESOLVED, - ), + let breach = Breach::new(dispute_tx, get_random_tx()); + let status = ConfirmationStatus::ConfirmedIn( + target_block_height - constants::IRREVOCABLY_RESOLVED, ); - responder - .gatekeeper - .get_registered_users() - .lock() - .unwrap() - .get_mut(&user_id) - .unwrap() - .appointments - .insert(uuid, 1); - - completed_trackers.insert(uuid, (user_id, breach)); + responder.add_tracker(uuid, breach.clone(), user_id, status); + completed_trackers.push(TransactionTracker::new(breach, user_id, status)); } // OUTDATED TRACKER SETUP - let mut penalties = Vec::new(); - let mut uuids = Vec::new(); - - for user_id in users.iter().take(21).skip(11) { - let pair = [generate_uuid(), generate_uuid()].to_vec(); - - for uuid in pair.iter() { - let (_, appointment) = generate_dummy_appointment_with_user(*user_id, None); + let mut outdated_trackers = Vec::new(); + for &user_id in users.iter().take(21).skip(11) { + for _ in 0..3 { + let dispute_tx = get_random_tx(); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + responder + .gatekeeper + .add_update_appointment(user_id, uuid, &appointment) + .unwrap(); responder .dbm .lock() .unwrap() - .store_appointment(*uuid, &appointment) + .store_appointment(uuid, &appointment) .unwrap(); - let breach = get_random_breach(); - penalties.push(breach.penalty_tx.txid()); - responder.add_tracker( - *uuid, - breach, - *user_id, - ConfirmationStatus::InMempoolSince(target_block_height - 1), - ); + let breach = Breach::new(dispute_tx, get_random_tx()); + let status = ConfirmationStatus::InMempoolSince(target_block_height - 1); + responder.add_tracker(uuid, breach.clone(), user_id, status); + outdated_trackers.push(TransactionTracker::new(breach, user_id, status)); } - uuids.extend(pair.clone()); + // Outdate this user so their trackers are deleted responder .gatekeeper - .add_outdated_user(*user_id, target_block_height, Some(pair)); + .add_outdated_user(user_id, target_block_height); } // CONFIRMATIONS SETUP @@ -1826,11 +1245,16 @@ mod tests { .add_update_user(standalone_user_id) .unwrap(); - let mut transactions = Vec::new(); - let mut just_confirmed_txs = Vec::new(); + let mut missed_confirmation_trackers = Vec::new(); + let mut just_confirmed_trackers = Vec::new(); for i in 0..10 { + let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(standalone_user_id, None); + generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.txid())); + responder + .gatekeeper + .add_update_appointment(standalone_user_id, uuid, &appointment) + .unwrap(); responder .dbm .lock() @@ -1838,39 +1262,53 @@ mod tests { .store_appointment(uuid, &appointment) .unwrap(); - let breach = get_random_breach(); - transactions.push(breach.clone().penalty_tx.txid()); + let breach = Breach::new(dispute_tx, get_random_tx()); + let status = ConfirmationStatus::InMempoolSince(target_block_height - 1); + responder.add_tracker(uuid, breach.clone(), standalone_user_id, status); if i % 2 == 0 { - just_confirmed_txs.push(breach.clone().penalty_tx); + just_confirmed_trackers.push(TransactionTracker::new( + breach, + standalone_user_id, + status, + )); + } else { + missed_confirmation_trackers.push(TransactionTracker::new( + breach, + standalone_user_id, + status, + )); } - responder.add_tracker( - uuid, - breach, - standalone_user_id, - ConfirmationStatus::InMempoolSince(target_block_height - 1), - ); } // REBROADCAST SETUP - let (uuid, appointment) = generate_dummy_appointment_with_user(standalone_user_id, None); + let mut trackers_to_rebroadcast = Vec::new(); + for _ in 0..5 { + let dispute_tx = get_random_tx(); + let (uuid, appointment) = + generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.txid())); + responder + .gatekeeper + .add_update_appointment(standalone_user_id, uuid, &appointment) + .unwrap(); + responder + .dbm + .lock() + .unwrap() + .store_appointment(uuid, &appointment) + .unwrap(); - responder - .dbm - .lock() - .unwrap() - .store_appointment(uuid, &appointment) - .unwrap(); - - let tracker_to_rebroadcast = uuid; - responder.add_tracker( - uuid, - get_random_breach(), - standalone_user_id, - ConfirmationStatus::InMempoolSince( + let breach = Breach::new(dispute_tx, get_random_tx()); + let status = ConfirmationStatus::InMempoolSince( target_block_height - CONFIRMATIONS_BEFORE_RETRY as u32, - ), - ); + ); + responder.add_tracker(uuid, breach.clone(), standalone_user_id, status); + trackers_to_rebroadcast.push(TransactionTracker::new( + breach, + standalone_user_id, + status, + )); + } // CARRIER CACHE SETUP // Add some dummy data in the cache to check that it gets cleared @@ -1882,10 +1320,16 @@ mod tests { .insert(get_random_tx().txid(), ConfirmationStatus::ConfirmedIn(21)); // Connecting a block should trigger all the state transitions - responder.block_connected( - &chain.generate(Some(just_confirmed_txs.clone())), - chain.get_block_count(), - ); + let block = chain.generate(Some( + just_confirmed_trackers + .iter() + .map(|t| t.penalty_tx.clone()) + .collect(), + )); + let height = chain.get_block_count(); + // We connect the gatekeeper first so it deletes the outdated users. + responder.gatekeeper.block_connected(&block, height); + responder.block_connected(&block, height); // CARRIER CHECKS assert!(responder @@ -1903,66 +1347,72 @@ mod tests { // COMPLETED TRACKERS CHECKS // Data should have been removed - for (uuid, (user_id, breach)) in completed_trackers { - assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - assert!(!responder - .tx_tracker_map + for tracker in completed_trackers { + assert!(responder + .dbm .lock() .unwrap() - .contains_key(&breach.penalty_tx.txid())); - assert!( - !responder.gatekeeper.get_registered_users().lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid) - ); + .load_tracker(tracker.uuid()) + .is_none()); + let (_, user_locators) = responder.gatekeeper.get_user_info(tracker.user_id).unwrap(); + assert!(!user_locators.contains(&tracker.locator())); } // OUTDATED TRACKERS CHECKS - // Data should have been removed - for uuid in uuids { - assert!(!responder.trackers.lock().unwrap().contains_key(&uuid)); - } - for txid in penalties { - assert!(!responder.tx_tracker_map.lock().unwrap().contains_key(&txid)); + // Data should have been removed (tracker not found nor the user) + for tracker in outdated_trackers { + assert!(responder + .dbm + .lock() + .unwrap() + .load_tracker(tracker.uuid()) + .is_none()); + assert!(responder + .gatekeeper + .get_user_info(tracker.user_id) + .is_none()); } // CONFIRMATIONS CHECKS // The transaction confirmation count / confirmation missed should have been updated - let tx_tracker_map = responder.tx_tracker_map.lock().unwrap(); - for txid in transactions { - let uuids = tx_tracker_map.get(&txid).unwrap(); - if just_confirmed_txs - .iter() - .map(|tx| tx.txid()) - .any(|x| x == txid) - { - for uuid in uuids.iter() { - assert_eq!( - responder.trackers.lock().unwrap()[uuid].status, - ConfirmationStatus::ConfirmedIn(target_block_height) - ); - } - } else { - for uuid in uuids.iter() { - assert_eq!( - responder.trackers.lock().unwrap()[uuid].status, - ConfirmationStatus::InMempoolSince(target_block_height - 1) - ); - } - } + for tracker in just_confirmed_trackers { + assert_eq!( + responder + .dbm + .lock() + .unwrap() + .load_tracker(tracker.uuid()) + .unwrap() + .status, + ConfirmationStatus::ConfirmedIn(target_block_height) + ); + } + for tracker in missed_confirmation_trackers { + assert_eq!( + responder + .dbm + .lock() + .unwrap() + .load_tracker(tracker.uuid()) + .unwrap() + .status, + ConfirmationStatus::InMempoolSince(target_block_height - 1) + ); } // REBROADCAST CHECKS - assert_eq!( - responder - .trackers - .lock() - .unwrap() - .get(&tracker_to_rebroadcast) - .unwrap() - .status, - ConfirmationStatus::InMempoolSince(target_block_height), - ); + for tracker in trackers_to_rebroadcast { + assert_eq!( + responder + .dbm + .lock() + .unwrap() + .load_tracker(tracker.uuid()) + .unwrap() + .status, + ConfirmationStatus::InMempoolSince(target_block_height), + ); + } } #[tokio::test] @@ -1974,21 +1424,16 @@ mod tests { // Add user to the database let user_id = get_random_user_id(); - responder - .dbm - .lock() - .unwrap() - .store_user( - user_id, - &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), - ) - .unwrap(); + responder.gatekeeper.add_update_user(user_id).unwrap(); let mut reorged = Vec::new(); + let block_range = START_HEIGHT - 10..START_HEIGHT; - for i in 0..10 { - // Generate appointment and also add it to the DB (FK checks) - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + for i in block_range.clone() { + // Generate appointment and also add it to the DB + let dispute_tx = get_random_tx(); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); responder .dbm .lock() @@ -1996,49 +1441,33 @@ mod tests { .store_appointment(uuid, &appointment) .unwrap(); - let breach = get_random_breach(); - + let breach = Breach::new(dispute_tx, get_random_tx()); responder.add_tracker( uuid, - breach.clone(), + breach, user_id, - ConfirmationStatus::ConfirmedIn(i), + ConfirmationStatus::ConfirmedIn(i as u32), ); reorged.push(uuid); } // Check that trackers are flagged as reorged if the height they were included at gets disconnected - for i in (0..10).rev() { + for (i, uuid) in block_range.clone().zip(reorged.iter()).rev() { // The header doesn't really matter, just the height - responder.block_disconnected(&chain.tip().header, i); + responder.block_disconnected(&chain.tip().header, i as u32); // Check that the proper tracker gets reorged at the proper height - assert_eq!( - responder - .trackers - .lock() - .unwrap() - .get(reorged.get(i as usize).unwrap()) - .unwrap() - .status, - ConfirmationStatus::ReorgedOut - ); - + assert!(responder.reorged_trackers.lock().unwrap().contains(uuid)); // Check that the carrier block_height has been updated - assert_eq!(responder.carrier.lock().unwrap().get_height(), i); + assert_eq!(responder.carrier.lock().unwrap().get_height(), i as u32); } // Check that all reorged trackers are still reorged - for uuid in reorged { - assert_eq!( - responder - .trackers - .lock() - .unwrap() - .get(&uuid) - .unwrap() - .status, - ConfirmationStatus::ReorgedOut - ); + for uuid in reorged.iter() { + assert!(responder.reorged_trackers.lock().unwrap().contains(uuid)); } + + // But should be clear after the first block connection + responder.block_connected(&chain.generate(None), block_range.start as u32); + assert!(responder.reorged_trackers.lock().unwrap().is_empty()); } } diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index b7959fd..4dba0a9 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -323,7 +323,7 @@ pub(crate) fn generate_dummy_appointment_with_user( let mut app = generate_dummy_appointment(dispute_txid); app.user_id = user_id; - (UUID::new(app.locator(), user_id), app) + (app.uuid(), app) } pub(crate) fn get_random_breach() -> Breach { @@ -341,17 +341,15 @@ pub(crate) fn get_random_tracker( TransactionTracker::new(breach, user_id, status) } -pub(crate) fn store_appointment_and_fks_to_db( - dbm: &DBM, - uuid: UUID, - appointment: &ExtendedAppointment, -) { +pub(crate) fn store_appointment_and_its_user(dbm: &DBM, appointment: &ExtendedAppointment) { dbm.store_user( appointment.user_id, &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), ) - .unwrap(); - dbm.store_appointment(uuid, appointment).unwrap(); + // It's ok if the user is already stored. + .ok(); + dbm.store_appointment(appointment.uuid(), appointment) + .unwrap(); } pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec { diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index fd7c2b9..a0e4a35 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -9,7 +9,7 @@ use lightning_block_sync::poll::ValidatedBlock; use teos_common::appointment::Locator; /// A trait implemented by types that can be used as key in a [TxIndex]. -pub trait Key: Hash { +pub trait Key: Hash + Eq { fn from_txid(txid: Txid) -> Self; } @@ -79,8 +79,8 @@ impl Value for Transaction { /// Data structure used to index locators computed from parsed blocks. /// /// Holds up to `size` blocks with their corresponding computed [Locator]s. -#[derive(Debug)] -pub struct TxIndex { +#[derive(Debug, PartialEq, Eq)] +pub struct TxIndex { /// A [K]:[V] map. index: HashMap, /// Vector of block hashes covered by the index. @@ -95,7 +95,7 @@ pub struct TxIndex { impl TxIndex where - K: Key + std::cmp::Eq + Copy, + K: Key + Copy, V: Value + Clone, Self: Sized, { @@ -143,11 +143,6 @@ where self.index.get(k) } - /// Checks whether the index contains a certain key. - pub fn contains_key(&self, k: &K) -> bool { - self.index.contains_key(k) - } - /// Checks if the index if full. pub fn is_full(&self) -> bool { self.blocks.len() > self.size @@ -175,7 +170,7 @@ where if self.is_full() { // Avoid logging during bootstrap - log::info!("New block added to index: {}", block_header.block_hash()); + log::debug!("New block added to index: {}", block_header.block_hash()); self.tip += 1; self.remove_oldest_block(); } @@ -204,11 +199,11 @@ where let ks = self.tx_in_block.remove(&h).unwrap(); self.index.retain(|k, _| !ks.contains(k)); - log::info!("Oldest block removed from index: {h}"); + log::debug!("Oldest block removed from index: {h}"); } } -impl fmt::Display for TxIndex { +impl fmt::Display for TxIndex { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -240,6 +235,10 @@ mod tests { pub fn blocks(&self) -> &VecDeque { &self.blocks } + + pub fn contains_key(&self, k: &K) -> bool { + self.index.contains_key(k) + } } #[tokio::test] @@ -304,7 +303,7 @@ mod tests { ); let fake_hash = BlockHash::default(); - assert!(matches!(cache.get_height(&fake_hash), None)); + assert!(cache.get_height(&fake_hash).is_none()); } #[tokio::test] diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 8a6f058..90606fc 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -1,10 +1,6 @@ //! Logic related to the Watcher, the components in charge of watching for breaches on chain. -use log; - -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; +use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; @@ -19,7 +15,7 @@ use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; use crate::dbm::DBM; -use crate::extended_appointment::{AppointmentSummary, ExtendedAppointment, UUID}; +use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::{Gatekeeper, MaxSlotsReached, UserInfo}; use crate::responder::{ConfirmationStatus, Responder, TransactionTracker}; use crate::tx_index::TxIndex; @@ -82,19 +78,11 @@ pub(crate) enum AppointmentInfo { Tracker(TransactionTracker), } -/// Reason why the appointment is deleted. Used for logging purposes. -enum DeletionReason { - Outdated, - Invalid, - Accepted, -} - /// Types of new appointments stored in the [Watcher]. #[derive(Debug, PartialEq, Eq)] enum StoredAppointment { New, Update, - Collision, } /// Types of new triggered appointments handled by the [Watcher]. @@ -108,10 +96,6 @@ enum TriggeredAppointment { /// Component in charge of watching for triggers in the chain (aka channel breaches for lightning). #[derive(Debug)] pub struct Watcher { - /// A map holding a summary of every appointment ([ExtendedAppointment]) hold by the [Watcher], identified by a [UUID]. - appointments: Mutex>, - /// A map between [Locator]s (user identifiers for [Appointment]s) and [UUID]s (tower identifiers). - locator_uuid_map: Mutex>>, /// A cache of the [Locator]s computed for the transactions in the last few blocks. locator_cache: Mutex>, /// A [Responder] instance. Data will be passed to it once triggered (if valid). @@ -139,20 +123,7 @@ impl Watcher { tower_id: TowerId, dbm: Arc>, ) -> Self { - let mut appointments = HashMap::new(); - let mut locator_uuid_map: HashMap> = HashMap::new(); - for (uuid, summary) in dbm.lock().unwrap().load_appointment_summaries() { - if let Some(map) = locator_uuid_map.get_mut(&summary.locator) { - map.insert(uuid); - } else { - locator_uuid_map.insert(summary.locator, HashSet::from_iter(vec![uuid])); - } - appointments.insert(uuid, summary); - } - Watcher { - appointments: Mutex::new(appointments), - locator_uuid_map: Mutex::new(locator_uuid_map), locator_cache: Mutex::new(TxIndex::new(last_n_blocks, last_known_block_height)), responder, gatekeeper, @@ -165,7 +136,7 @@ impl Watcher { /// Returns whether the [Watcher] has been created from scratch (fresh) or from backed-up data. pub fn is_fresh(&self) -> bool { - self.appointments.lock().unwrap().is_empty() + self.get_appointments_count() == 0 } /// Registers a new user within the [Watcher]. This request is passed to the [Gatekeeper], who is in @@ -185,8 +156,7 @@ impl Watcher { /// - The user has enough available slots to fit the appointment /// - The appointment hasn't been responded to yet (data cannot be found in the [Responder]) /// - /// If an appointment is accepted, an [AppointmentSummary] will be added to the the watching pool and - /// monitored by the [Watcher]. An [ExtendedAppointment] (constructed from the [Appointment]) will be persisted on disk. + /// If an appointment is accepted, an [ExtendedAppointment] (constructed from the [Appointment]) will be persisted on disk. /// In case the locator for the given appointment can be found in the cache (meaning the appointment has been /// triggered recently) the data will be passed to the [Responder] straightaway (modulo it being valid). pub(crate) fn add_appointment( @@ -213,13 +183,15 @@ impl Watcher { self.last_known_block_height.load(Ordering::Acquire), ); - let uuid = UUID::new(extended_appointment.locator(), user_id); + let uuid = extended_appointment.uuid(); if self.responder.has_tracker(uuid) { log::info!("Tracker for {uuid} already found in Responder"); return Err(AddAppointmentFailure::AlreadyTriggered); } + // TODO: This is not atomic, we update the users slots and THEN add their appointment + // this means it can happen that we update the slots but some failure happens before we insert their appointment. let available_slots = self .gatekeeper .add_update_appointment(user_id, uuid, &extended_appointment) @@ -254,54 +226,23 @@ impl Watcher { Ok((receipt, available_slots, expiry)) } - /// Stores an appointment in the [Watcher] memory and into the database (or updates it if it already exists). - /// - /// Data is stored in `locator_uuid_map` and `appointments`. + /// Stores an appointment in the database (or updates it if it already exists). fn store_appointment( &self, uuid: UUID, appointment: &ExtendedAppointment, ) -> StoredAppointment { - self.appointments - .lock() - .unwrap() - .insert(uuid, appointment.get_summary()); - let mut locator_uuid_map = self.locator_uuid_map.lock().unwrap(); - if let Entry::Vacant(e) = locator_uuid_map.entry(appointment.locator()) { - // New appointment - e.insert(HashSet::from_iter(vec![uuid])); - - self.dbm - .lock() - .unwrap() - .store_appointment(uuid, appointment) - .unwrap(); - StoredAppointment::New + let dbm = self.dbm.lock().unwrap(); + if dbm.appointment_exists(uuid) { + log::debug!( + "User {} is updating appointment {uuid}", + appointment.user_id + ); + dbm.update_appointment(uuid, appointment).unwrap(); + StoredAppointment::Update } else { - // Either an update or an appointment from another user sharing the same locator - if locator_uuid_map - .get_mut(&appointment.locator()) - .unwrap() - .insert(uuid) - { - log::debug!( - "Adding an additional appointment to locator {}: {uuid}", - appointment.locator() - ); - self.dbm - .lock() - .unwrap() - .store_appointment(uuid, appointment) - .unwrap(); - StoredAppointment::Collision - } else { - log::debug!("Update received for {uuid}, locator map not modified"); - self.dbm - .lock() - .unwrap() - .update_appointment(uuid, appointment); - StoredAppointment::Update - } + dbm.store_appointment(uuid, appointment).unwrap(); + StoredAppointment::New } } @@ -328,6 +269,9 @@ impl Watcher { .lock() .unwrap() .store_appointment(uuid, appointment) + // TODO: Don't unwrap, or better, make this insertion atomic with the + // `responder.has_tracker` that might cause the unwrap in the first place. + // ref: https://github.com/talaia-labs/rust-teos/pull/190#discussion_r1218235632 .unwrap(); if let ConfirmationStatus::Rejected(reason) = self.responder.handle_breach( @@ -335,11 +279,8 @@ impl Watcher { Breach::new(dispute_tx.clone(), penalty_tx), user_id, ) { - // DISCUSS: We could either free the slots or keep it occupied as if this was misbehavior. - // Keeping it for now. log::warn!("Appointment bounced in the Responder. Reason: {reason:?}"); - - self.dbm.lock().unwrap().remove_appointment(uuid); + self.gatekeeper.delete_appointments(vec![uuid], false); TriggeredAppointment::Rejected } else { log::info!("Appointment went straight to the Responder"); @@ -388,25 +329,17 @@ impl Watcher { } let uuid = UUID::new(locator, user_id); - - if self.appointments.lock().unwrap().contains_key(&uuid) { - Ok(AppointmentInfo::Appointment( - self.dbm - .lock() - .unwrap() - .load_appointment(uuid) - .unwrap() - .inner, - )) - } else { - self.responder - .get_tracker(uuid) - .map(AppointmentInfo::Tracker) - .ok_or_else(|| { - log::info!("Cannot find {locator}"); - GetAppointmentFailure::NotFound - }) - } + let dbm = self.dbm.lock().unwrap(); + dbm.load_tracker(uuid) + .map(AppointmentInfo::Tracker) + .or_else(|| { + dbm.load_appointment(uuid) + .map(|ext_app| AppointmentInfo::Appointment(ext_app.inner)) + }) + .ok_or_else(|| { + log::info!("Cannot find {locator}"); + GetAppointmentFailure::NotFound + }) } /// Gets a map of breaches provided a map between locators and transactions. @@ -418,20 +351,14 @@ impl Watcher { &self, locator_tx_map: HashMap, ) -> HashMap { - let monitored_locators: HashSet = self - .locator_uuid_map + let breaches: HashMap = self + .dbm .lock() .unwrap() - .keys() - .cloned() + .batch_check_locators_exist(locator_tx_map.keys().collect()) + .iter() + .map(|locator| (*locator, locator_tx_map[locator].clone())) .collect(); - let new_locators = locator_tx_map.keys().cloned().collect(); - let mut breaches = HashMap::new(); - - for locator in monitored_locators.intersection(&new_locators) { - let (k, v) = locator_tx_map.get_key_value(locator).unwrap(); - breaches.insert(*k, v.clone()); - } if breaches.is_empty() { log::info!("No breaches found") @@ -442,117 +369,38 @@ impl Watcher { breaches } - /// Filters a map of breaches between those that are valid and those that are not. + /// Responds to breaches. /// - /// Valid breaches are those resulting in a properly formatted [Transaction] once decrypted. - fn filter_breaches( - &self, - breaches: HashMap, - ) -> ( - HashMap, - HashMap, - ) { - let mut valid_breaches = HashMap::new(); - let mut invalid_breaches = HashMap::new(); + /// Decrypts triggered appointments using the dispute transaction ID and publishes them. + /// If the decryption fails for some appointments or if it succeeds but they get rejected when sent to the network, + /// they are marked as an invalid breaches and returned. + /// [None] is returned if none of these breaches are invalid. + fn handle_breaches(&self, breaches: HashMap) -> Option> { + let mut invalid_breaches = Vec::new(); - // A cache of the already decrypted blobs so replicate decryption can be avoided - let mut decrypted_blobs: HashMap, Transaction> = HashMap::new(); - - let locator_uuid_map = self.locator_uuid_map.lock().unwrap(); - let dbm = self.dbm.lock().unwrap(); for (locator, dispute_tx) in breaches.into_iter() { - for uuid in locator_uuid_map.get(&locator).unwrap() { - let appointment = dbm.load_appointment(*uuid).unwrap(); - match decrypted_blobs.get(appointment.encrypted_blob()) { - Some(penalty_tx) => { - valid_breaches - .insert(*uuid, Breach::new(dispute_tx.clone(), penalty_tx.clone())); - } - None => { - match cryptography::decrypt( - appointment.encrypted_blob(), - &dispute_tx.txid(), + // WARNING(deadlock): Don't lock `self.dbm` over the loop since `Responder::handle_breach` uses it as well. + let uuids = self.dbm.lock().unwrap().load_uuids(locator); + for uuid in uuids { + let appointment = self.dbm.lock().unwrap().load_appointment(uuid).unwrap(); + match cryptography::decrypt(appointment.encrypted_blob(), &dispute_tx.txid()) { + Ok(penalty_tx) => { + if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( + uuid, + Breach::new(dispute_tx.clone(), penalty_tx), + appointment.user_id, ) { - Ok(penalty_tx) => { - decrypted_blobs.insert( - appointment.encrypted_blob().clone(), - penalty_tx.clone(), - ); - valid_breaches - .insert(*uuid, Breach::new(dispute_tx.clone(), penalty_tx)); - } - Err(e) => { - invalid_breaches.insert(*uuid, e); - } + invalid_breaches.push(uuid); } } - } - } - } - - (valid_breaches, invalid_breaches) - } - - // DISCUSS:: For outdated data this may be nicer if implemented with a callback from the GK given that: - // - The GK is queried for the data to be deleted - // - Appointment and tracker data can be deleted in cascade when a user is deleted - // If done, the GK can notify the Watcher and Responder to delete data in memory and - // take care of the database itself. - - /// Deletes appointments from memory. - /// - /// The appointments are deleted from the appointments and locator_uuid_map maps. - /// Logs a different message depending on whether the appointments have been outdated, invalid, or accepted. - fn delete_appointments_from_memory(&self, uuids: &HashSet, reason: DeletionReason) { - let mut appointments = self.appointments.lock().unwrap(); - let mut locator_uuid_map = self.locator_uuid_map.lock().unwrap(); - - for uuid in uuids { - match reason { - DeletionReason::Outdated => { - log::info!("End time reached by {uuid} without breach. Deleting appointment") - } - DeletionReason::Invalid => log::info!( - "{uuid} cannot be completed, it contains invalid data. Deleting appointment" - ), - DeletionReason::Accepted => { - log::info!("{uuid} accepted by the Responder. Deleting appointment") - } - }; - match appointments.remove(uuid) { - Some(appointment) => { - let appointments = locator_uuid_map.get_mut(&appointment.locator).unwrap(); - - if appointments.len() == 1 { - locator_uuid_map.remove(&appointment.locator); - - log::info!("No more appointments for locator: {}", appointment.locator); - } else { - appointments.remove(uuid); + Err(_) => { + invalid_breaches.push(uuid); } } - None => { - // This should never happen. Logging just in case so we can fix it if so - log::error!("Appointment not found when cleaning: {uuid}"); - } } } - } - /// Deletes appointments from memory and the database. - fn delete_appointments( - &self, - uuids: &HashSet, - updated_users: &HashMap, - reason: DeletionReason, - ) { - if !uuids.is_empty() { - self.delete_appointments_from_memory(uuids, reason); - self.dbm - .lock() - .unwrap() - .batch_remove_appointments(uuids, updated_users); - } + (!invalid_breaches.is_empty()).then_some(invalid_breaches) } /// Ges the number of users currently registered with the tower. @@ -560,9 +408,9 @@ impl Watcher { self.gatekeeper.get_registered_users_count() } - /// Gets the total number of appointments stored in the [Watcher]. + /// Gets the total number of appointments excluding trackers. pub(crate) fn get_appointments_count(&self) -> usize { - self.appointments.lock().unwrap().len() + self.dbm.lock().unwrap().get_appointments_count() } /// Gets the total number of trackers in the [Responder]. @@ -602,7 +450,7 @@ impl Watcher { } /// Gets the data held by the tower about a given user. - pub(crate) fn get_user_info(&self, user_id: UserId) -> Option { + pub(crate) fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { self.gatekeeper.get_user_info(user_id) } @@ -625,28 +473,7 @@ impl Watcher { return Err(GetSubscriptionInfoFailure::SubscriptionExpired(expiry)); } - let subscription_info = self.gatekeeper.get_user_info(user_id).unwrap(); - let mut locators = Vec::new(); - - let appointments = self.appointments.lock().unwrap(); - let dbm = self.dbm.lock().unwrap(); - for uuid in subscription_info.appointments.keys() { - match appointments.get(uuid) { - Some(a) => locators.push(a.locator), - None => { - if self.responder.has_tracker(*uuid) { - if let Some(locator) = dbm.load_locator(*uuid) { - locators.push(locator) - } else { - log::error!("Tracker found in Responder but not in DB (uuid = {uuid})") - } - } else { - log::error!("Appointment found in the Gatekeeper but not in the Watcher nor the Responder (uuid = {uuid})") - } - } - } - } - + let (subscription_info, locators) = self.gatekeeper.get_user_info(user_id).unwrap(); Ok((subscription_info, locators)) } } @@ -682,54 +509,9 @@ impl chain::Listen for Watcher { .unwrap() .update(*header, &locator_tx_map); - if !self.appointments.lock().unwrap().is_empty() { - // Start by removing outdated data so it is not taken into account from this point on - self.delete_appointments_from_memory( - &self.gatekeeper.get_outdated_appointments(height), - DeletionReason::Outdated, - ); - - // Filter out those breaches that do not yield a valid transaction - let (valid_breaches, invalid_breaches) = - self.filter_breaches(self.get_breaches(locator_tx_map)); - - // Send data to the Responder - let mut appointments_to_delete = HashSet::from_iter(invalid_breaches.into_keys()); - let mut delivered_appointments = HashSet::new(); - for (uuid, breach) in valid_breaches { - log::info!("Notifying Responder and deleting appointment (uuid: {uuid})"); - - if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( - uuid, - breach, - self.appointments.lock().unwrap()[&uuid].user_id, - ) { - appointments_to_delete.insert(uuid); - } else { - delivered_appointments.insert(uuid); - } - } - - // Delete data - let appointments_to_delete_gatekeeper = { - let appointments = self.appointments.lock().unwrap(); - appointments_to_delete - .iter() - .map(|uuid| (*uuid, appointments[uuid].user_id)) - .collect() - }; - self.delete_appointments_from_memory(&delivered_appointments, DeletionReason::Accepted); - self.delete_appointments( - &appointments_to_delete, - &self - .gatekeeper - .delete_appointments_from_memory(&appointments_to_delete_gatekeeper), - DeletionReason::Invalid, - ); - - if self.appointments.lock().unwrap().is_empty() { - log::info!("No more pending appointments"); - } + // Get the breaches found in this block, handle them, and delete invalid ones. + if let Some(invalid_breaches) = self.handle_breaches(self.get_breaches(locator_tx_map)) { + self.gatekeeper.delete_appointments(invalid_breaches, false); } // Update last known block @@ -754,6 +536,8 @@ impl chain::Listen for Watcher { #[cfg(test)] mod tests { use super::*; + use std::collections::HashSet; + use std::iter::FromIterator; use std::ops::Deref; use std::sync::{Arc, Mutex}; @@ -762,42 +546,35 @@ mod tests { use crate::rpc_errors; use crate::test_utils::{ create_carrier, create_responder, create_watcher, generate_dummy_appointment, - generate_dummy_appointment_with_user, generate_uuid, get_random_breach, get_random_tx, - store_appointment_and_fks_to_db, BitcoindMock, BitcoindStopper, Blockchain, MockOptions, - MockedServerQuery, AVAILABLE_SLOTS, DURATION, EXPIRY_DELTA, SLOTS, START_HEIGHT, - SUBSCRIPTION_EXPIRY, SUBSCRIPTION_START, + generate_dummy_appointment_with_user, get_random_tx, BitcoindMock, BitcoindStopper, + Blockchain, MockOptions, MockedServerQuery, DURATION, EXPIRY_DELTA, SLOTS, START_HEIGHT, }; - use teos_common::cryptography::{get_random_bytes, get_random_keypair}; + use teos_common::cryptography::get_random_keypair; - use bitcoin::hash_types::Txid; - use bitcoin::hashes::Hash; use bitcoin::secp256k1::{PublicKey, Secp256k1}; use lightning::chain::Listen; impl PartialEq for Watcher { fn eq(&self, other: &Self) -> bool { - *self.appointments.lock().unwrap() == *other.appointments.lock().unwrap() - && *self.locator_uuid_map.lock().unwrap() == *other.locator_uuid_map.lock().unwrap() - && self.last_known_block_height.load(Ordering::Relaxed) - == other.last_known_block_height.load(Ordering::Relaxed) + // Same in-memory data. + self.last_known_block_height.load(Ordering::Relaxed) == other.last_known_block_height.load(Ordering::Relaxed) && + *self.locator_cache.lock().unwrap() == *other.locator_cache.lock().unwrap() && + // && Same DB data. + self.get_all_watcher_appointments() == other.get_all_watcher_appointments() } } impl Eq for Watcher {} impl Watcher { - pub(crate) fn add_dummy_tracker_to_responder( - &self, - uuid: UUID, - tracker: &TransactionTracker, - ) { - self.responder.add_dummy_tracker(uuid, tracker) + pub(crate) fn add_dummy_tracker_to_responder(&self, tracker: &TransactionTracker) { + self.responder.add_dummy_tracker(tracker) } - pub(crate) fn add_random_tracker_to_responder(&self, uuid: UUID) -> TransactionTracker { + pub(crate) fn add_random_tracker_to_responder(&self) -> TransactionTracker { // The confirmation status can be whatever here. Using the most common. self.responder - .add_random_tracker(uuid, ConfirmationStatus::ConfirmedIn(100)) + .add_random_tracker(ConfirmationStatus::ConfirmedIn(100)) } } @@ -858,11 +635,11 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); watcher.register(user_id).unwrap(); - let appointment = generate_dummy_appointment(None).inner; - // If we add some trackers to the system and create a new Responder reusing the same db + // If we add some appointments to the system and create a new Watcher reusing the same db // (as if simulating a bootstrap from existing data), the data should be properly loaded. for _ in 0..10 { + let appointment = generate_dummy_appointment(None).inner; let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher .add_appointment(appointment.clone(), user_sig.clone()) @@ -913,7 +690,6 @@ mod tests { // - if the appointment already exists for a given user, update the data // - if the appointment is already in the Responder, reject // - if the trigger for the appointment is in the cache, trigger straightaway - // - DISCUSS: if the appointment is accepted but bounces in the Responder, do not reduce the subscription count // In any of the cases where the appointment should be added to the Watcher, the appointment will be rejected if: // - the user does not have enough slots (either to add or update) // - the subscription has expired @@ -926,10 +702,10 @@ mod tests { let user_id = UserId(user_pk); watcher.register(user_id).unwrap(); let appointment = generate_dummy_appointment(None).inner; + let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); // Add the appointment for a new user (twice so we can check that updates work) for _ in 0..2 { - let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(appointment.clone(), user_sig.clone()) .unwrap(); @@ -949,32 +725,25 @@ mod tests { assert_appointment_added(slots, SLOTS - 1, expiry, receipt, &user2_sig, tower_id); - // There should be now two appointments in the Watcher and the same locator should have two different uuids - assert_eq!(watcher.appointments.lock().unwrap().len(), 2); - assert_eq!( - watcher.locator_uuid_map.lock().unwrap()[&appointment.locator].len(), - 2 - ); - - // Check data was added to the database - for uuid in watcher.appointments.lock().unwrap().keys() { - assert!(watcher - .dbm - .lock() - .unwrap() - .load_appointment(*uuid) - .is_some()); - } + // There should be now two appointments in the Watcher + assert_eq!(watcher.get_appointments_count(), 2); + assert_eq!(watcher.responder.get_trackers_count(), 0); // If an appointment is already in the Responder, it should bounce - let (uuid, triggered_appointment) = generate_dummy_appointment_with_user(user_id, None); + let dispute_tx = get_random_tx(); + let (uuid, triggered_appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); let signature = cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk).unwrap(); - watcher + let (receipt, slots, expiry) = watcher .add_appointment(triggered_appointment.inner.clone(), signature.clone()) .unwrap(); - let breach = get_random_breach(); + assert_appointment_added(slots, SLOTS - 2, expiry, receipt, &signature, tower_id); + assert_eq!(watcher.get_appointments_count(), 3); + assert_eq!(watcher.responder.get_trackers_count(), 0); + + let breach = Breach::new(dispute_tx, get_random_tx()); watcher.responder.add_tracker( uuid, breach, @@ -987,6 +756,8 @@ mod tests { receipt, Err(AddAppointmentFailure::AlreadyTriggered) )); + assert_eq!(watcher.get_appointments_count(), 2); + assert_eq!(watcher.responder.get_trackers_count(), 1); // If the trigger is already in the cache, the appointment will go straight to the Responder let dispute_tx = tip_txs.last().unwrap(); @@ -994,24 +765,16 @@ mod tests { generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); let user_sig = cryptography::sign(&appointment_in_cache.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher - .add_appointment(appointment_in_cache.inner.clone(), user_sig.clone()) + .add_appointment(appointment_in_cache.inner, user_sig.clone()) .unwrap(); - // The appointment should have been accepted, slots should have been decreased, and data should have been deleted from - // the Watcher's memory. Moreover, a new tracker should be found in the Responder + // The appointment should have been accepted, slots should have been decreased, and a new tracker should be found in the Responder assert_appointment_added(slots, SLOTS - 3, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.appointments.lock().unwrap().len(), 3); - assert!(!watcher - .locator_uuid_map - .lock() - .unwrap() - .contains_key(&appointment_in_cache.locator())); + assert_eq!(watcher.get_appointments_count(), 2); + assert_eq!(watcher.responder.get_trackers_count(), 2); + // Data should be in the database assert!(watcher.responder.has_tracker(uuid)); - // Check data was added to the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); - assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_some()); - // If an appointment is rejected by the Responder, it is considered misbehavior and the slot count is kept // Wrong penalty let dispute_tx = &tip_txs[tip_txs.len() - 2]; @@ -1020,15 +783,15 @@ mod tests { invalid_appointment.inner.encrypted_blob.reverse(); let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher - .add_appointment(invalid_appointment.inner.clone(), user_sig.clone()) + .add_appointment(invalid_appointment.inner, user_sig.clone()) .unwrap(); assert_appointment_added(slots, SLOTS - 4, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.appointments.lock().unwrap().len(), 3); - + assert_eq!(watcher.get_appointments_count(), 2); + assert_eq!(watcher.responder.get_trackers_count(), 2); // Data should not be in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); - assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_none()); + assert!(!watcher.responder.has_tracker(uuid)); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // Transaction rejected // Update the Responder with a new Carrier @@ -1039,17 +802,19 @@ mod tests { *watcher.responder.get_carrier().lock().unwrap() = carrier; let dispute_tx = &tip_txs[tip_txs.len() - 2]; - let invalid_appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; - let user_sig = cryptography::sign(&invalid_appointment.to_vec(), &user_sk).unwrap(); + let (uuid, invalid_appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher - .add_appointment(invalid_appointment, user_sig.clone()) + .add_appointment(invalid_appointment.inner, user_sig.clone()) .unwrap(); - assert_appointment_added(slots, SLOTS - 4, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.appointments.lock().unwrap().len(), 3); - + assert_appointment_added(slots, SLOTS - 5, expiry, receipt, &user_sig, tower_id); + assert_eq!(watcher.get_appointments_count(), 2); + assert_eq!(watcher.responder.get_trackers_count(), 2); // Data should not be in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.responder.has_tracker(uuid)); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // FAIL cases (non-registered, subscription expired and not enough slots) @@ -1058,11 +823,11 @@ mod tests { let user3_sig = String::from_utf8((0..65).collect()).unwrap(); assert!(matches!( - watcher.add_appointment(appointment.clone(), user3_sig), + watcher.add_appointment(appointment, user3_sig), Err(AddAppointmentFailure::AuthenticationFailure) )); // Data should not be in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // If the user has no enough slots, the appointment is rejected. We do not test all possible cases since updates are // already tested int he Gatekeeper. Testing that it is rejected if the condition is met should suffice. @@ -1075,33 +840,30 @@ mod tests { .unwrap() .available_slots = 0; - let dispute_txid = Txid::from_slice(&get_random_bytes(32)).unwrap(); - let new_appointment = generate_dummy_appointment(Some(&dispute_txid)).inner; - let new_app_sig = cryptography::sign(&new_appointment.to_vec(), &user_sk).unwrap(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + let signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); assert!(matches!( - watcher.add_appointment(new_appointment, new_app_sig), + watcher.add_appointment(appointment.inner, signature), Err(AddAppointmentFailure::NotEnoughSlots) )); // Data should not be in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // If the user subscription has expired, the appointment should be rejected. watcher .gatekeeper - .get_registered_users() - .lock() - .unwrap() - .get_mut(&user2_id) - .unwrap() - .subscription_expiry = START_HEIGHT as u32; + .add_outdated_user(user2_id, START_HEIGHT as u32); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, None); + let signature = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); assert!(matches!( - watcher.add_appointment(appointment, user2_sig), + watcher.add_appointment(appointment.inner, signature), Err(AddAppointmentFailure::SubscriptionExpired { .. }) )); // Data should not be in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); } #[tokio::test] @@ -1113,8 +875,10 @@ mod tests { let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); watcher.register(user_id).unwrap(); + let dispute_txid = get_random_tx().txid(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); // Storing a new appointment should return New assert_eq!( @@ -1122,46 +886,22 @@ mod tests { StoredAppointment::New, ); assert_eq!( - *watcher.appointments.lock().unwrap(), - HashMap::from_iter([(uuid, appointment.get_summary())]) - ); - assert_eq!( - *watcher.locator_uuid_map.lock().unwrap(), - HashMap::from_iter([(appointment.locator(), HashSet::from_iter([uuid]))]) + watcher.get_all_watcher_appointments(), + HashMap::from_iter([(uuid, appointment)]) ); // Adding an appointment with the same UUID should be seen as an updated - // The appointment data here does not matter much, just the UUID and the locator since they are tied to each other. + // We are using a common dispute txid here to get the same uuid. + let (new_uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); + assert_eq!(new_uuid, uuid); assert_eq!( watcher.store_appointment(uuid, &appointment), StoredAppointment::Update, ); assert_eq!( - *watcher.appointments.lock().unwrap(), - HashMap::from_iter([(uuid, appointment.get_summary())]) - ); - assert_eq!( - *watcher.locator_uuid_map.lock().unwrap(), - HashMap::from_iter([(appointment.locator(), HashSet::from_iter([uuid]))]) - ); - - // Adding the same appointment (same locator) with a different UUID should be seen as a collision. - // This means that a different user is sending an appointment with the same locator. - let new_uuid = generate_uuid(); - assert_eq!( - watcher.store_appointment(new_uuid, &appointment), - StoredAppointment::Collision, - ); - assert_eq!( - *watcher.appointments.lock().unwrap(), - HashMap::from_iter([ - (uuid, appointment.get_summary()), - (new_uuid, appointment.get_summary()) - ]) - ); - assert_eq!( - *watcher.locator_uuid_map.lock().unwrap(), - HashMap::from_iter([(appointment.locator(), HashSet::from_iter([uuid, new_uuid]))]) + watcher.get_all_watcher_appointments(), + HashMap::from_iter([(uuid, appointment)]) ); } @@ -1186,7 +926,7 @@ mod tests { ); // In this case the appointment is kept in the Responder and, therefore, in the database assert!(watcher.responder.has_tracker(uuid)); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); + assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); // A properly formatted but invalid transaction should be rejected by the Responder // Update the Responder with a new Carrier that will reject the transaction @@ -1204,19 +944,19 @@ mod tests { ); // In this case the appointment is not kept in the Responder nor in the database assert!(!watcher.responder.has_tracker(uuid)); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // Invalid triggered appointments should not be passed to the Responder // Use a dispute_tx that does not match the appointment to replicate a decryption error // (the same applies to invalid formatted transactions) - let uuid = generate_uuid(); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); assert_eq!( - watcher.store_triggered_appointment(uuid, &appointment, user_id, &get_random_tx()), + watcher.store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx), TriggeredAppointment::Invalid, ); // The appointment is not kept anywhere assert!(!watcher.responder.has_tracker(uuid)); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); } #[tokio::test] @@ -1224,9 +964,10 @@ mod tests { let mut chain = Blockchain::default().with_height(START_HEIGHT); let (watcher, _s) = init_watcher(&mut chain).await; - let appointment = generate_dummy_appointment(None).inner; + let dispute_tx = get_random_tx(); + let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; - // If the user cannot be properly identified, the request will fail. This can be simulated by providing a wrong signature + // If the user cannot be properly identified, the request will fail. This can be simulated by providing a wrong signature let wrong_sig = String::from_utf8((0..65).collect()).unwrap(); assert!(matches!( watcher.get_appointment(appointment.locator, &wrong_sig), @@ -1259,29 +1000,16 @@ mod tests { // If the appointment is in the Responder (in the form of a Tracker), data should be also returned - // Remove the data from the Watcher memory first (data is kept in the db tho) + // Remove the data from the Watcher memory first. let uuid = UUID::new(appointment.locator, user_id); - watcher.appointments.lock().unwrap().remove(&uuid); - watcher - .locator_uuid_map - .lock() - .unwrap() - .remove(&appointment.locator); // Add data to the Responder - let breach = get_random_breach(); - let tracker = TransactionTracker::new( - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(chain.get_block_count()), - ); - - watcher.responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::InMempoolSince(chain.get_block_count()), - ); + let breach = Breach::new(dispute_tx, get_random_tx()); + let status = ConfirmationStatus::InMempoolSince(chain.get_block_count()); + watcher + .responder + .add_tracker(uuid, breach.clone(), user_id, status); + let tracker = TransactionTracker::new(breach, user_id, status); let tracker_message = format!("get appointment {}", appointment.locator); let tracker_signature = cryptography::sign(tracker_message.as_bytes(), &user_sk).unwrap(); @@ -1296,8 +1024,8 @@ mod tests { AppointmentInfo::Tracker(t) => assert_eq!(t, tracker), } - // If the user does exists but the requested locator does not belong to any of their associated appointments, NotFound - // should be returned. + // If the user does exists but the requested locator does not belong to any of their associated appointments, + // NotFound should be returned. let (user2_sk, user2_pk) = get_random_keypair(); let user2_id = UserId(user2_pk); watcher.register(user2_id).unwrap(); @@ -1311,12 +1039,7 @@ mod tests { // If the user subscription has expired, the request will fail watcher .gatekeeper - .get_registered_users() - .lock() - .unwrap() - .get_mut(&user_id) - .unwrap() - .subscription_expiry = START_HEIGHT as u32; + .add_outdated_user(user_id, START_HEIGHT as u32); assert!(matches!( watcher.get_appointment(appointment.locator, &signature), @@ -1327,276 +1050,168 @@ mod tests { #[tokio::test] async fn test_get_breaches() { let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); - let txs = chain.blocks.last().unwrap().txdata.clone(); let (watcher, _s) = init_watcher(&mut chain).await; // Let's create some locators based on the transactions in the last block - let mut locator_tx_map = HashMap::new(); - for tx in txs { - locator_tx_map.insert(Locator::new(tx.txid()), tx.clone()); - } + let locator_tx_map: HashMap<_, _> = (0..10) + .map(|_| get_random_tx()) + .map(|tx| (Locator::new(tx.txid()), tx)) + .collect(); + + let (user_sk, user_pk) = get_random_keypair(); + let user_id = UserId(user_pk); + watcher.register(user_id).unwrap(); // Add some of them to the Watcher - for (i, locator) in locator_tx_map.keys().enumerate() { + let mut breaches = HashMap::new(); + for (i, (l, tx)) in locator_tx_map.iter().enumerate() { + // Track some of the these transactions. if i % 2 == 0 { - watcher - .locator_uuid_map - .lock() - .unwrap() - .insert(*locator, HashSet::from_iter(vec![generate_uuid()])); + let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + watcher.add_appointment(appointment, signature).unwrap(); + breaches.insert(*l, tx.clone()); } } // Check that breaches are correctly detected - let breaches = watcher.get_breaches(locator_tx_map); - let locator_uuid_map = watcher.locator_uuid_map.lock().unwrap(); - assert!( - breaches.len() == locator_uuid_map.len() - && breaches.keys().all(|k| locator_uuid_map.contains_key(k)) - ); + assert_eq!(watcher.get_breaches(locator_tx_map), breaches); } #[tokio::test] - async fn test_filter_breaches() { - let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 12); - let txs = chain.blocks.last().unwrap().txdata.clone(); + async fn test_handle_breaches_accepted() { + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); let (watcher, _s) = init_watcher(&mut chain).await; // Let's create some locators based on the transactions in the last block - let mut locator_tx_map = HashMap::new(); - for tx in txs { - locator_tx_map.insert(Locator::new(tx.txid()), tx.clone()); + let breaches: HashMap<_, _> = (0..10) + .map(|_| get_random_tx()) + .map(|tx| (Locator::new(tx.txid()), tx)) + .collect(); + + let (user_sk, user_pk) = get_random_keypair(); + let user_id = UserId(user_pk); + watcher.register(user_id).unwrap(); + + // Let the watcher track these breaches. + for (_, tx) in breaches.iter() { + let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + watcher.add_appointment(appointment, signature).unwrap(); } - // Add some of them to the Watcher - let mut local_valid = Vec::new(); - let mut local_invalid = Vec::new(); - - for (i, (locator, tx)) in locator_tx_map.iter().enumerate() { - let uuid = generate_uuid(); - let tx_id = tx.txid(); - let mut dispute_txid = None; - - // Add 1/3 as valid breaches, 1/3 as invalid, leave 1/3 out - if i % 3 < 2 { - match i % 3 { - 0 => { - dispute_txid = Some(&tx_id); - local_valid.push(uuid); - } - _ => local_invalid.push(uuid), - } - - let appointment = generate_dummy_appointment(dispute_txid); - - watcher - .appointments - .lock() - .unwrap() - .insert(uuid, appointment.get_summary()); - watcher - .locator_uuid_map - .lock() - .unwrap() - .insert(*locator, HashSet::from_iter(vec![uuid])); - - // Store data in the database (the user needs to be there as well since it is a FK for appointments) - store_appointment_and_fks_to_db(&watcher.dbm.lock().unwrap(), uuid, &appointment); - } - } - - let breaches = watcher.get_breaches(locator_tx_map.clone()); - let (valid, invalid) = watcher.filter_breaches(breaches); - - // Check valid + invalid add up to 2/3 - assert_eq!(2 * locator_tx_map.len() / 3, valid.len() + invalid.len()); - - // Check valid breaches match - assert!(valid.len() == local_valid.len() && valid.keys().all(|k| local_valid.contains(k))); - - // Check invalid breaches match - assert!( - invalid.len() == local_invalid.len() - && invalid.keys().all(|k| local_invalid.contains(k)) - ); - - // All invalid breaches should be AED errors (the decryption key was invalid) - invalid - .values() - .all(|v| matches!(v, cryptography::DecryptingError::AED { .. })); + assert!(watcher.handle_breaches(breaches).is_none()) } #[tokio::test] - async fn test_delete_appointments_from_memory() { - let mut chain = Blockchain::default().with_height(START_HEIGHT); + async fn test_handle_breaches_rejected_decryption() { + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); let (watcher, _s) = init_watcher(&mut chain).await; - // Add some appointments both to memory and to the database - let mut to_be_deleted = HashMap::new(); + // Let's create some locators based on the transactions in the last block + let breaches: HashMap<_, _> = (0..10) + .map(|_| get_random_tx()) + .map(|tx| (Locator::new(tx.txid()), tx)) + .collect(); - for _ in 0..10 { - let uuid = generate_uuid(); - let appointment = generate_dummy_appointment(None); - watcher - .appointments - .lock() - .unwrap() - .insert(uuid, appointment.get_summary()); - watcher - .locator_uuid_map - .lock() - .unwrap() - .insert(appointment.locator(), HashSet::from_iter([uuid])); + let (user_sk, user_pk) = get_random_keypair(); + let user_id = UserId(user_pk); + watcher.register(user_id).unwrap(); - store_appointment_and_fks_to_db(&watcher.dbm.lock().unwrap(), uuid, &appointment); - to_be_deleted.insert(uuid, appointment.locator()); - } - - // Delete and check data is not in memory (the reason does not matter for the test) - watcher.delete_appointments_from_memory( - &to_be_deleted.keys().cloned().collect(), - DeletionReason::Outdated, - ); - - for (uuid, locator) in to_be_deleted { - // Data is not in memory - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(!watcher - .locator_uuid_map - .lock() - .unwrap() - .contains_key(&locator)); - - // But it can be found in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); - } - } - - #[tokio::test] - async fn test_delete_appointments() { - // TODO: This is an adaptation of Responder::test_delete_trackers, merge together once the method - // is implemented using generics. - let mut chain = Blockchain::default().with_height(START_HEIGHT); - let (watcher, _s) = init_watcher(&mut chain).await; - - // Delete appointments removes data from the appointments and locator_uuid_map - // Add data to the map first - let mut all_appointments = HashSet::new(); - let mut target_appointments = HashSet::new(); - let mut uuid_locator_map = HashMap::new(); - let mut locator_with_multiple_uuids = HashSet::new(); - let mut updated_users = HashMap::new(); - - for i in 0..10 { - let uuid = generate_uuid(); - let appointment = generate_dummy_appointment(None); - watcher - .appointments - .lock() - .unwrap() - .insert(uuid, appointment.clone().get_summary()); - watcher - .locator_uuid_map - .lock() - .unwrap() - .insert(appointment.locator(), HashSet::from_iter([uuid])); - - // Add data to the database to check data deletion - store_appointment_and_fks_to_db(&watcher.dbm.lock().unwrap(), uuid, &appointment); - - // Make it so some of the locators have multiple associated uuids - if i % 3 == 0 { - // We don't need to store this properly since they will not be targeted - let uuid2 = generate_uuid(); - watcher - .locator_uuid_map - .lock() - .unwrap() - .get_mut(&appointment.locator()) - .unwrap() - .insert(uuid2); - locator_with_multiple_uuids.insert(appointment.locator()); - } - - all_appointments.insert(uuid); - uuid_locator_map.insert(uuid, appointment.locator()); - - // Add some appointments to be deleted + let mut rejected = HashSet::new(); + // Let the watcher track these breaches. + for (i, (_, tx)) in breaches.iter().enumerate() { + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + let mut appointment = appointment.inner; if i % 2 == 0 { - // Users will also be updated once the data is deleted. - // We can made up the numbers here just to check they are updated. - target_appointments.insert(uuid); - updated_users.insert( - appointment.user_id, - UserInfo::new( - AVAILABLE_SLOTS + i, - SUBSCRIPTION_START + i, - SUBSCRIPTION_EXPIRY + i, - ), - ); - } + // Mal-format some appointments + appointment.encrypted_blob.reverse(); + rejected.insert(uuid); + }; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + watcher.add_appointment(appointment, signature).unwrap(); } - // The deletion reason does not matter here, it only changes the logged message when deleting data - watcher.delete_appointments( - &target_appointments, - &updated_users, - DeletionReason::Accepted, + assert_eq!( + rejected, + HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) ); + } - // Only appointments in the target_appointments map should have been removed from - // the Watcher's data structures. - for uuid in all_appointments { - if target_appointments.contains(&uuid) { - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + #[tokio::test] + async fn test_handle_breaches_rejected_by_responder_backend() { + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); + let (watcher, _s) = init_watcher(&mut chain).await; - let locator = &uuid_locator_map[&uuid]; - // If the penalty had more than one associated uuid, only one has been deleted - // (because that's how the test has been designed) - if locator_with_multiple_uuids.contains(locator) { - assert_eq!( - watcher - .locator_uuid_map - .lock() - .unwrap() - .get(locator) - .unwrap() - .len(), - 1 - ); - } else { - // Otherwise the whole structure is removed, given it is now empty - assert!(!watcher - .locator_uuid_map - .lock() - .unwrap() - .contains_key(locator)); - } - } else { - assert!(watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(watcher - .locator_uuid_map - .lock() - .unwrap() - .contains_key(&uuid_locator_map[&uuid])); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); - } + // Replace the carrier with an erroneous one + let (carrier, _s) = create_carrier( + MockedServerQuery::Error(rpc_errors::RPC_VERIFY_ERROR as i64), + chain.tip().deref().height, + ); + *watcher.responder.get_carrier().lock().unwrap() = carrier; + + // Let's create some locators based on the transactions in the last block + let breaches: HashMap<_, _> = (0..10) + .map(|_| get_random_tx()) + .map(|tx| (Locator::new(tx.txid()), tx)) + .collect(); + + let (user_sk, user_pk) = get_random_keypair(); + let user_id = UserId(user_pk); + watcher.register(user_id).unwrap(); + + let mut uuids = HashSet::new(); + // Let the watcher track these breaches. + for (_, (_, tx)) in breaches.iter().enumerate() { + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + let appointment = appointment.inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + watcher.add_appointment(appointment, signature).unwrap(); + uuids.insert(uuid); } - // The users that needed to be updated in the database have been (just checking the slot count) - for (id, info) in updated_users { - assert_eq!( - watcher - .dbm - .lock() - .unwrap() - .load_user(id) - .unwrap() - .available_slots, - info.available_slots - ); + assert_eq!( + uuids, + HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) + ); + } + + #[tokio::test] + async fn test_handle_breaches_rejected_by_responder_malformed() { + let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); + let (watcher, _s) = init_watcher(&mut chain).await; + + // Let's create some locators based on the transactions in the last block + let breaches: HashMap<_, _> = (0..10) + .map(|_| get_random_tx()) + .map(|tx| (Locator::new(tx.txid()), tx)) + .collect(); + + let (user_sk, user_pk) = get_random_keypair(); + let user_id = UserId(user_pk); + watcher.register(user_id).unwrap(); + + let mut rejected_breaches = HashSet::new(); + // Let the watcher track these breaches. + for (i, (_, tx)) in breaches.iter().enumerate() { + let (uuid, appointment) = + generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + let mut appointment = appointment.inner; + if i % 2 == 0 { + // Mal-format some appointments, they should be returned as rejected. + appointment.encrypted_blob.reverse(); + rejected_breaches.insert(uuid); + }; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + watcher.add_appointment(appointment, signature).unwrap(); } + + assert_eq!( + rejected_breaches, + HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) + ); } #[tokio::test] @@ -1622,9 +1237,11 @@ mod tests { // If there are appointments to watch, the Watcher will: // - Check if any new transaction is a trigger // - Check if a trigger is valid, if so pass the data to the Responder - // - Delete invalid appointments. + // - Delete invalid appointments (decryption error or rejection by responder). // - Delete appointments that have been outdated (i.e. have expired without a trigger) - // - Delete invalid appointments also from the Gatekeeper (not outdated tough, the GK will take care of those via it's own Listen) + // - Delete invalid appointments also from the Gatekeeper + // + // We will also test that appointments for outdated users are removed by the GK. // Let's first check how data gets outdated (create two users, add an appointment to both and outdate only one) let (user_sk, user_pk) = get_random_keypair(); @@ -1634,177 +1251,111 @@ mod tests { watcher.register(user_id).unwrap(); watcher.register(user2_id).unwrap(); - let appointment = generate_dummy_appointment(None); - let uuid1 = UUID::new(appointment.locator(), user_id); - let uuid2 = UUID::new(appointment.locator(), user2_id); + let appointment = generate_dummy_appointment(None).inner; + let uuid1 = UUID::new(appointment.locator, user_id); + let uuid2 = UUID::new(appointment.locator, user2_id); - let user_sig = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); + let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher - .add_appointment(appointment.inner.clone(), user_sig) - .unwrap(); - let user2_sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - watcher - .add_appointment(appointment.inner.clone(), user2_sig) + .add_appointment(appointment.clone(), user_sig) .unwrap(); + let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk).unwrap(); + watcher.add_appointment(appointment, user2_sig).unwrap(); + // Outdate the first user's registration. watcher + .gatekeeper + .add_outdated_user(user_id, chain.get_block_count()); + + // Both appointments can be found before mining a block, only the user's 2 can be found afterwards + for &uuid in &[uuid1, uuid2] { + assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); + } + + // We always need to connect the gatekeeper first so it cleans up outdated users and their data. + let block = chain.generate(None); + watcher + .gatekeeper + .block_connected(&block, chain.get_block_count()); + watcher.block_connected(&block, chain.get_block_count()); + + // uuid1 and user1 should have been deleted while uuid2 and user2 still exists. + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid1)); + assert!(!watcher .gatekeeper .get_registered_users() .lock() .unwrap() - .get_mut(&user_id) - .unwrap() - .subscription_expiry = chain.get_block_count() - EXPIRY_DELTA + 1; - - // Both appointments can be found before mining a block, only the user's 2 can be found afterwards - for uuid in &[uuid1, uuid2] { - assert!(watcher.appointments.lock().unwrap().contains_key(uuid)); - assert!( - watcher.locator_uuid_map.lock().unwrap()[&appointment.locator()].contains(uuid) - ); - } - assert!( - watcher.gatekeeper.get_registered_users().lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid1) - ); - assert!( - watcher.gatekeeper.get_registered_users().lock().unwrap()[&user2_id] - .appointments - .contains_key(&uuid2) - ); - - watcher.block_connected(&chain.generate(None), chain.get_block_count()); - - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid1)); - assert!(!watcher.locator_uuid_map.lock().unwrap()[&appointment.locator()].contains(&uuid1)); - // Data is still in the Gatekeeper and in the database, since it'll be deleted in cascade by the - // Gatekeeper on user's deletion (given the user was outdated in the test). - assert!( - watcher.gatekeeper.get_registered_users().lock().unwrap()[&user_id] - .appointments - .contains_key(&uuid1) - ); + .contains_key(&user_id)); + assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid2)); assert!(watcher - .dbm + .gatekeeper + .get_registered_users() .lock() .unwrap() - .load_appointment(uuid1) - .is_some()); - - assert!(watcher.appointments.lock().unwrap().contains_key(&uuid2)); - assert!(watcher.locator_uuid_map.lock().unwrap()[&appointment.locator()].contains(&uuid2)); - assert!( - watcher.gatekeeper.get_registered_users().lock().unwrap()[&user2_id] - .appointments - .contains_key(&uuid2) - ); - assert!(watcher - .dbm - .lock() - .unwrap() - .load_appointment(uuid2) - .is_some()); + .contains_key(&user2_id)); // Check triggers. Add a new appointment and trigger it with valid data. let dispute_tx = get_random_tx(); - let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - let uuid = UUID::new(appointment.locator(), user2_id); watcher.add_appointment(appointment.inner, sig).unwrap(); - assert!(watcher.appointments.lock().unwrap().contains_key(&uuid)); + assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); - watcher.block_connected( - &chain.generate(Some(vec![dispute_tx])), - chain.get_block_count(), - ); - - // Data should have been moved to the Responder and kept in the Gatekeeper, since it is still part of the system. - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(watcher - .responder - .get_trackers() - .lock() - .unwrap() - .contains_key(&uuid)); - assert!( - watcher.gatekeeper.get_registered_users().lock().unwrap()[&user2_id] - .appointments - .contains_key(&uuid) - ); + let block = chain.generate(Some(vec![dispute_tx])); + watcher + .gatekeeper + .block_connected(&block, chain.get_block_count()); + watcher.block_connected(&block, chain.get_block_count()); // Data should have been kept in the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_some()); - assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_some()); + assert!(watcher.responder.has_tracker(uuid)); + + // Checks invalid triggers. Add a new appointment and trigger it with invalid data. + let dispute_tx = get_random_tx(); + let (uuid, mut appointment) = + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); + // Modify the encrypted blob so the data is invalid. + appointment.inner.encrypted_blob.reverse(); + let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); + watcher.add_appointment(appointment.inner, sig).unwrap(); + + let block = chain.generate(Some(vec![dispute_tx])); + watcher + .gatekeeper + .block_connected(&block, chain.get_block_count()); + watcher.block_connected(&block, chain.get_block_count()); + + // Data should have been wiped from the database + assert!(!watcher.responder.has_tracker(uuid)); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); // Check triggering with a valid formatted transaction but that is rejected by the Responder. let dispute_tx = get_random_tx(); - let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())); + let (uuid, appointment) = + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - let uuid = UUID::new(appointment.locator(), user2_id); watcher.add_appointment(appointment.inner, sig).unwrap(); // Set the carrier response - let (carrier, _as) = create_carrier( + // Both non-decryptable blobs and blobs with invalid transactions will yield an invalid trigger. + let (carrier, _s) = create_carrier( MockedServerQuery::Error(rpc_errors::RPC_VERIFY_ERROR as i64), chain.tip().deref().height, ); *watcher.responder.get_carrier().lock().unwrap() = carrier; - watcher.block_connected( - &chain.generate(Some(vec![dispute_tx])), - chain.get_block_count(), - ); - - // Data should not be in the Responder, in the Watcher nor in the Gatekeeper - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(!watcher - .responder - .get_trackers() - .lock() - .unwrap() - .contains_key(&uuid)); - assert!( - !watcher.gatekeeper.get_registered_users().lock().unwrap()[&user2_id] - .appointments - .contains_key(&uuid) - ); - // Data should also have been deleted from the database - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); - assert!(watcher.dbm.lock().unwrap().load_tracker(uuid).is_none()); - - // Checks invalid triggers. Add a new appointment and trigger it with invalid data. - let dispute_tx = get_random_tx(); - let mut appointment = generate_dummy_appointment(Some(&dispute_tx.txid())); - // Modify the encrypted blob so the data is invalid. - //Both non-decryptable blobs and blobs with invalid transactions will yield an invalid trigger - appointment.inner.encrypted_blob.reverse(); - let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - let uuid = UUID::new(appointment.locator(), user2_id); + let block = chain.generate(Some(vec![dispute_tx])); watcher - .add_appointment(appointment.inner.clone(), sig) - .unwrap(); + .gatekeeper + .block_connected(&block, chain.get_block_count()); + watcher.block_connected(&block, chain.get_block_count()); - watcher.block_connected( - &chain.generate(Some(vec![dispute_tx])), - chain.get_block_count(), - ); - - // Data has been wiped since it was invalid - assert!(!watcher.appointments.lock().unwrap().contains_key(&uuid)); - assert!(!watcher - .responder - .get_trackers() - .lock() - .unwrap() - .contains_key(&uuid)); - assert!( - !watcher.gatekeeper.get_registered_users().lock().unwrap()[&user2_id] - .appointments - .contains_key(&uuid) - ); - assert!(watcher.dbm.lock().unwrap().load_appointment(uuid).is_none()); + // Data should have been wiped from the database + assert!(!watcher.responder.has_tracker(uuid)); + assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); } #[tokio::test] From 1790fe31e0b651c0a1dce23d8ff7ba474cc42c49 Mon Sep 17 00:00:00 2001 From: Omer Yacine Date: Wed, 30 Aug 2023 17:13:48 +0300 Subject: [PATCH 098/119] Make random appointments variable in size --- teos-common/src/test_utils.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index dd0a333..0b5a95b 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -5,10 +5,9 @@ use rand::distributions::Standard; use rand::prelude::Distribution; use rand::Rng; -use bitcoin::consensus; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; -use bitcoin::Txid; +use bitcoin::{consensus, Script, Transaction, TxOut, Txid}; use crate::appointment::{Appointment, Locator}; use crate::cryptography; @@ -48,7 +47,15 @@ pub fn generate_random_appointment(dispute_txid: Option<&Txid>) -> Appointment { }; let tx_bytes = Vec::from_hex(TX_HEX).unwrap(); - let penalty_tx = consensus::deserialize(&tx_bytes).unwrap(); + let mut penalty_tx: Transaction = consensus::deserialize(&tx_bytes).unwrap(); + + // Append a random-sized OP_RETURN to make each transcation random in size. + penalty_tx.output.push(TxOut { + value: 0, + script_pubkey: Script::new_op_return(&cryptography::get_random_bytes( + get_random_int::() % 81, + )), + }); let mut raw_locator: [u8; 16] = cryptography::get_random_bytes(16).try_into().unwrap(); raw_locator.copy_from_slice(&dispute_txid[..16]); From 96efd4320c2c355f41e434e388c2bc1bba47b9e7 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 7 Jul 2023 13:41:21 -0500 Subject: [PATCH 099/119] Docker: Add Dockerfile for running teos --- docker/Dockerfile | 49 ++++++++++++++++++++++++++++++++ docker/entrypoint.sh | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 docker/Dockerfile create mode 100755 docker/entrypoint.sh diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..1333ea8 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,49 @@ +# Use the rust image as the base image for the build stage +FROM rust:latest AS builder + +# Copy the rust-teos source code +COPY . /tmp/rust-teos + +# Install the dependencies required for building rust-teos +RUN apt-get update\ + && apt-get -y --no-install-recommends install libffi-dev libssl-dev musl-tools pkg-config + +RUN cd /tmp/rust-teos \ + && rustup target add x86_64-unknown-linux-musl \ + # Rustfmt is needed to format the grpc stubs generated by tonic + && rustup component add rustfmt \ + # Cross compile with musl as the target, so teosd can run on alpine + && RUSTFLAGS='-C target-feature=+crt-static' cargo build --manifest-path=teos/Cargo.toml --locked --release --target x86_64-unknown-linux-musl + +# Use a new stage with a smaller base image to reduce image size +FROM alpine:latest + +RUN apk update && apk upgrade + +# UID and GID for the teosd user +ENV TEOS_UID=1001 TEOS_GID=1001 + +# Copy the teos binaries from the build stage to the new stage +COPY --from=builder \ + /tmp/rust-teos/target/x86_64-unknown-linux-musl/release/teosd \ + /tmp/rust-teos/target/x86_64-unknown-linux-musl/release/teos-cli /usr/local/bin/ + +# Copy the entrypoint script to the container +COPY docker/entrypoint.sh /entrypoint.sh + +# Set the entrypoint script as executable and add running user +RUN chmod +x /entrypoint.sh \ + && addgroup -g ${TEOS_GID} -S teos \ + && adduser -S -G teos -u ${TEOS_UID} teos + +# Expose the default port used by teosd +EXPOSE 9814/tcp + +# Switch user so that we don't run stuff as root +USER teos + +# Create the teos data directory +RUN mkdir /home/teos/.teos + +# Start teosd when the container starts +ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..f8975af --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# Define the start command +START_COMMAND="teosd" + +# Set the API bind address +if [[ ! -z ${API_BIND} ]]; then + START_COMMAND="$START_COMMAND --apibind $API_BIND" +fi + +# Set the API port +if [[ ! -z ${API_PORT} ]]; then + START_COMMAND="$START_COMMAND --apiport $API_PORT" +fi + +# Set the RPC bind address +if [[ ! -z ${RPC_BIND} ]]; then + START_COMMAND="$START_COMMAND --rpcbind $RPC_BIND" +fi + +# Set the RPC port +if [[ ! -z ${RPC_PORT} ]]; then + START_COMMAND="$START_COMMAND --rpcport $RPC_PORT" +fi + +# Set the Bitcoin network +if [[ ! -z ${BTC_NETWORK} ]]; then + START_COMMAND="$START_COMMAND --btcnetwork $BTC_NETWORK" +fi + +# Set the Bitcoin RPC credentials +if [[ ! -z ${BTC_RPC_USER} ]]; then + START_COMMAND="$START_COMMAND --btcrpcuser $BTC_RPC_USER" +fi + +if [[ ! -z ${BTC_RPC_PASSWORD} ]]; then + START_COMMAND="$START_COMMAND --btcrpcpassword $BTC_RPC_PASSWORD" +fi + +# Set the Bitcoin RPC connection details +if [[ ! -z ${BTC_RPC_CONNECT} ]]; then + START_COMMAND="$START_COMMAND --btcrpcconnect $BTC_RPC_CONNECT" +fi + +if [[ ! -z ${BTC_RPC_PORT} ]]; then + START_COMMAND="$START_COMMAND --btcrpcport $BTC_RPC_PORT" +fi + +if [ "${DEBUG}" == "true" ]; then + START_COMMAND="$START_COMMAND --debug" +fi + +if [ "${DEPS_DEBUG}" == "true" ]; then + START_COMMAND="$START_COMMAND --depsdebug" +fi + +if [ "${OVERWRITE_KEY}" == "true" ]; then + START_COMMAND="$START_COMMAND --overwritekey" +fi + +if [ "${FORCE_UPDATE}" == "true" ]; then + START_COMMAND="$START_COMMAND --forceupdate" +fi + +# Start the TEOS daemon +$START_COMMAND From ddd4bd9ef18be31095839bb17263fe7f27a55a7d Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 7 Jul 2023 13:42:02 -0500 Subject: [PATCH 100/119] Docker: Add README describing how to use Docker with teos --- README.md | 2 + docker/README.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 docker/README.md diff --git a/README.md b/README.md index 9b5f754..0336b48 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Refer to [INSTALL.md](INSTALL.md) Make sure `bitcoind` is running before running `teosd` (it will fail at startup if it cannot connect to `bitcoind`). [Here](DEPENDENCIES.md#installing-bitcoind) you can find a sample bitcoin.conf. +Please see [Docker instructions](docker/README.md) for instructions on how to set up `teosd` in Docker. + ### Starting the tower daemon â™– Once installed, you can start the tower by running: diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..6c4289a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,113 @@ +## Running `teosd` in a docker container +A `teos` image can be built from the Dockerfile located in `docker`. You can create the image by running: + + cd rust-teos + docker build -f docker/Dockerfile -t teos . + +Then we can create a container by running: + + docker run -it teos + +One way to feed `teos` custom config options is to set environment variables: + + docker run -it -e teos + +Notice that the ENV variables are optional, if unset the corresponding default setting is used. The following ENVs are available: + +``` +- API_BIND= +- API_PORT= +- RPC_BIND= +- RPC_PORT= +- BTC_NETWORK= +- BTC_RPC_CONNECT= +- BTC_RPC_PORT= +- BTC_RPC_USER= +- BTC_RPC_PASSWORD= +# The following options can be set turned on by setting them to "true" +- DEBUG= +- DEPS_DEBUG= +- OVERWRITE_KEY= +- FORCE_UPDATE= +``` + +### Volume persistence + +You may also want to run docker with a volume, so you can have data persistence in `teosd` databases and keys. +If so, run: + + docker volume create teos-data + +And add the the mount parameter to `docker run`: + + -v teos-data:/home/teos/.teos + +If you are running `teosd` and `bitcoind` in the same machine, continue reading for how to create the container based on your OS. + +### `bitcoind` running on the same machine (UNIX) +The easiest way to run both together in the same machine using UNIX is to set the container to use the host network. + +For example, if both `teosd` and `bitcoind` are running on default settings, run: + +``` +docker run \ + --network=host \ + --name teos \ + -v teos-data:/home/teos/.teos \ + -e BTC_RPC_USER= \ + -e BTC_RPC_PASSWORD= \ + -it teos +``` + +Notice that you may still need to set your RPC authentication details, since, hopefully, your credentials won't match the `teosd` defaults. + +### `bitcoind` running on the same machine (OSX or Windows) + +Docker for OSX and Windows does not allow to use the host network (nor to use the `docker0` bridge interface). To work around this +you can use the special `host.docker.internal` domain: + +``` +docker run \ + -p 9814:9814 \ + -p 8814:8814 \ + --name teos \ + -v teos-data:/home/teos/.teos \ + -e BTC_RPC_CONNECT=host.docker.internal \ + -e BTC_RPC_USER= \ + -e BTC_RPC_PASSWORD= \ + -e API_BIND=0.0.0.0 \ + -e RPC_BIND=0.0.0.0 \ + -it teos +``` + +Notice that we also needed to add `API_BIND=0.0.0.0` and `RPC_BIND=0.0.0.0` to bind the API to all interfaces of the container. +Otherwise it will bind to `localhost` and we won't be able to send requests to the tower from the host. + +### Interacting with a TEOS instance + +Once our `teos` instance is running in the container, we can interact with it using `teos-cli`. We have two main ways of doing so: + +1) You can open a shell to the Docker instance by calling: + +`docker exec -it sh` + +Then you can use the `teos-cli` binary from inside the container as you would use it from your host machine. + +2) Using `teos-cli` remotely (assuming you have it installed in the source machine) and pointing to the container. To do so, you will need to copy over the necessary credentials to the host machine. To do so, you can follow the instructions in [the main README](https://github.com/talaia-labs/rust-teos/blob/master/README.md#running-teos-cli-remotely). + +### Plugging in Tor + +You may have noticed, in the above section where the environment variables are covered, that the Tor options are nowhere to be found. That's because these instructions assume that users will likely be setting up Tor in another container. + +On the machine where you have Tor running, you can follow [these instructions](https://community.torproject.org/onion-services/setup/) for setting up a hidden service manually. + +For instance, if you're running `teosd` in a Docker container on the same machine as where Tor is running, you can create a hidden service from the host machine to hide the IP of the `teosd` API (listening on port 9814 for example). If you're using Linux, you can do so by editing your `torrc` file on the host machine with the below option: + +``` +HiddenServiceDir /var/lib/tor/teosd # Path for Linux. This may differ depending on your OS. +HiddenServicePort 9814 127.0.0.1:9814 +``` + +Then restart Tor. + +If all works correctly, the hidden service public key will be located in the `HiddenServiceDir` you set above, in the file called `hostname`. From e6495c31aa114b308a4e0731e41f903cbe84fb7b Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 8 Jan 2024 13:38:59 -0500 Subject: [PATCH 101/119] Fixes clippy issues Clippy is complaining about using `.get(0)` instead of `.fist()` in methods where we are getting more than just the first item. Suppress those warning. Also fixes some actual issues. --- teos/src/tx_index.rs | 2 +- teos/src/watcher.rs | 2 +- watchtower-plugin/src/convert.rs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index a0e4a35..c9d22b4 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -276,7 +276,7 @@ mod tests { // last_n_blocks is ordered from latest to earliest let first_block = last_n_blocks.get(cache_size - 1).unwrap(); - let last_block = last_n_blocks.get(0).unwrap(); + let last_block = last_n_blocks.first().unwrap(); let mid = last_n_blocks.get(cache_size / 2).unwrap(); let cache: TxIndex = TxIndex::new(&last_n_blocks, height as u32); diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 90606fc..037319d 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -1163,7 +1163,7 @@ mod tests { let mut uuids = HashSet::new(); // Let the watcher track these breaches. - for (_, (_, tx)) in breaches.iter().enumerate() { + for tx in breaches.values() { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); let appointment = appointment.inner; diff --git a/watchtower-plugin/src/convert.rs b/watchtower-plugin/src/convert.rs index ec1b9d0..77ca95a 100644 --- a/watchtower-plugin/src/convert.rs +++ b/watchtower-plugin/src/convert.rs @@ -94,6 +94,8 @@ impl RegisterParams { impl TryFrom for RegisterParams { type Error = RegisterError; + // clippy-fix: We are getting more than just the first item, so this clippy check does not make sense here + #[allow(clippy::get_first)] fn try_from(value: serde_json::Value) -> Result { match value { serde_json::Value::String(s) => { @@ -193,6 +195,8 @@ pub struct GetAppointmentParams { impl TryFrom for GetAppointmentParams { type Error = GetAppointmentError; + // clippy-fix: We are getting more than just the first item, so this clippy check does not make sense here + #[allow(clippy::get_first)] fn try_from(value: serde_json::Value) -> Result { match value { serde_json::Value::Array(a) => { From 534d6390ba9dde35b02bf50b379db1a8a44acd32 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 28 Mar 2024 09:23:56 +0100 Subject: [PATCH 102/119] watchtower-plugin: fixes pyln-client version `pyln-testing` depends on `pyln-client`, and the API for the latter has changed in version 24.0. Fix our dependency to 23.11 to prevent test from breaking --- watchtower-plugin/tests/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/watchtower-plugin/tests/pyproject.toml b/watchtower-plugin/tests/pyproject.toml index 2760baa..abcb2e4 100644 --- a/watchtower-plugin/tests/pyproject.toml +++ b/watchtower-plugin/tests/pyproject.toml @@ -13,6 +13,7 @@ black = "^22.6.0" pytest = "^7.1.2" pytest-timeout = "^2.1.0" pyln-testing = "^0.12.1" +pyln-client = "^23.11" [build-system] From 52ebbe6e068ca4e26d0d612ef2a23e093a2c9447 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 20 Mar 2024 15:47:24 -0400 Subject: [PATCH 103/119] Fixes clippy issues in dbm.rs --- teos/src/dbm.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index ca6dabf..a36c9df 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -181,7 +181,7 @@ impl DBM { } /// Removes some users from the database in batch. - pub(crate) fn batch_remove_users(&mut self, users: &Vec) -> usize { + pub(crate) fn batch_remove_users(&mut self, users: &[UserId]) -> usize { let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; let tx = self.connection.transaction().unwrap(); let iter = users @@ -417,7 +417,7 @@ impl DBM { /// update is atomic. pub(crate) fn batch_remove_appointments( &mut self, - appointments: &Vec, + appointments: &[UUID], updated_users: &HashMap, ) -> usize { let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; @@ -935,7 +935,7 @@ mod tests { Ok { .. } )); - dbm.batch_remove_users(&vec![appointment.user_id]); + dbm.batch_remove_users(&[appointment.user_id]); assert!(dbm.load_user(appointment.user_id).is_none()); assert!(dbm.load_appointment(uuid).is_none()); @@ -947,7 +947,7 @@ mod tests { )); assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); - dbm.batch_remove_users(&vec![appointment.user_id]); + dbm.batch_remove_users(&[appointment.user_id]); assert!(dbm.load_user(appointment.user_id).is_none()); assert!(dbm.load_appointment(uuid).is_none()); assert!(dbm.load_tracker(uuid).is_none()); @@ -956,7 +956,7 @@ mod tests { #[test] fn test_batch_remove_nonexistent_users() { let mut dbm = DBM::in_memory().unwrap(); - let users = (0..10).map(|_| get_random_user_id()).collect(); + let users = (0..10).map(|_| get_random_user_id()).collect::>(); // Test it does not fail even if the user does not exist (it will log though) dbm.batch_remove_users(&users); @@ -1286,10 +1286,7 @@ mod tests { Ok { .. } )); - dbm.batch_remove_appointments( - &vec![uuid], - &HashMap::from_iter([(appointment.user_id, info)]), - ); + dbm.batch_remove_appointments(&[uuid], &HashMap::from_iter([(appointment.user_id, info)])); assert!(dbm.load_appointment(uuid).is_none()); // Appointment + Tracker @@ -1299,10 +1296,7 @@ mod tests { )); assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); - dbm.batch_remove_appointments( - &vec![uuid], - &HashMap::from_iter([(appointment.user_id, info)]), - ); + dbm.batch_remove_appointments(&[uuid], &HashMap::from_iter([(appointment.user_id, info)])); assert!(dbm.load_appointment(uuid).is_none()); assert!(dbm.load_tracker(uuid).is_none()); } @@ -1310,7 +1304,7 @@ mod tests { #[test] fn test_batch_remove_nonexistent_appointments() { let mut dbm = DBM::in_memory().unwrap(); - let appointments = (0..10).map(|_| generate_uuid()).collect(); + let appointments = (0..10).map(|_| generate_uuid()).collect::>(); // Test it does not fail even if the user does not exist (it will log though) dbm.batch_remove_appointments(&appointments, &HashMap::new()); From 99dd5bbd15a8651741f33df9d85d318a923edab1 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 28 Mar 2024 09:50:34 +0100 Subject: [PATCH 104/119] gh-actions: updates actions to use Node.js 20 --- .github/workflows/build.yaml | 13 +++++-------- .github/workflows/cln-plugin.yaml | 12 ++++++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5a90464..5eba801 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,13 +16,11 @@ jobs: runs-on: ${{ matrix.platform }} steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust ${{ matrix.toolchain }} toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - override: true - profile: minimal - name: Build on Rust ${{ matrix.toolchain }} run: | cargo build ${{ matrix.arguments }} --verbose --color always @@ -34,12 +32,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust stable toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: stable - profile: minimal components: rustfmt, clippy - name: Run rustfmt run: | @@ -52,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run black uses: psf/black@stable with: diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index b31482d..b09064d 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -10,14 +10,14 @@ jobs: cache-cln: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' check-latest: true - name: Create CLN cache id: cache-cln - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-cln-dev with: @@ -37,8 +37,8 @@ jobs: needs: cache-cln runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' check-latest: true @@ -49,7 +49,7 @@ jobs: ln -s $(pwd)/bitcoin-${{ env.bitcoind_version }}/bin/bitcoin* /usr/local/bin - name: Load CLN cache id: cache-cln - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-cln-dev with: From 37a75ec6e74a1c355efda7883c06de13d625aa07 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 8 Jan 2024 13:19:21 -0500 Subject: [PATCH 105/119] Adds rpccookie to bitcoind auth methods Currently we are authenticating against bitcoind using user/pass. This adds the option to use a cookie file instead. --- teos/src/bitcoin_cli.rs | 32 +++++++++++++++++---- teos/src/conf_template.toml | 2 ++ teos/src/config.rs | 55 +++++++++++++++++++++++++++++++------ teos/src/main.rs | 16 ++++++++--- 4 files changed, 88 insertions(+), 17 deletions(-) diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 80b805c..22b5338 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -18,6 +18,7 @@ use bitcoin::base64; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::hashes::hex::ToHex; use bitcoin::{Block, Transaction}; +use bitcoincore_rpc::Auth; use lightning::util::ser::Writeable; use lightning_block_sync::http::{HttpEndpoint, JsonResponse}; use lightning_block_sync::rpc::RpcClient; @@ -32,9 +33,9 @@ pub struct BitcoindClient<'a> { /// The port to connect to. port: u16, /// The RPC user `bitcoind` is configured with. - rpc_user: &'a str, + rpc_user: String, /// The RPC password for the given user. - rpc_password: &'a str, + rpc_password: String, } impl BlockSource for &BitcoindClient<'_> { @@ -74,12 +75,33 @@ impl<'a> BitcoindClient<'a> { pub async fn new( host: &'a str, port: u16, - rpc_user: &'a str, - rpc_password: &'a str, + auth: Auth, teos_network: &'a str, ) -> std::io::Result> { let http_endpoint = HttpEndpoint::for_host(host.to_owned()).with_port(port); - let rpc_credentials = base64::encode(&format!("{rpc_user}:{rpc_password}")); + let (rpc_user, rpc_password) = { + let (user, pass) = auth.get_user_pass().map_err(|e| { + Error::new( + ErrorKind::InvalidInput, + format!("Cannot read cookie file. {}", e), + ) + })?; + if user.is_none() { + Err(Error::new( + ErrorKind::InvalidInput, + "Empty btc_rpc_user parsed from rpc_cookie".to_string(), + )) + } else if pass.is_none() { + Err(Error::new( + ErrorKind::InvalidInput, + "Empty btc_rpc_password parsed from rpc_cookie", + )) + } else { + Ok((user.unwrap(), pass.unwrap())) + } + }?; + + let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password)); let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; let client = Self { diff --git a/teos/src/conf_template.toml b/teos/src/conf_template.toml index 9540cb4..6193b35 100644 --- a/teos/src/conf_template.toml +++ b/teos/src/conf_template.toml @@ -12,8 +12,10 @@ rpc_port = 8814 # bitcoind btc_network = "mainnet" btc_rpc_user = "CSW" +## Notice only user+password **OR** cookie is allowed as rpc auth, any other combination would be rejected btc_rpc_password = "NotSatoshi" btc_rpc_connect = "localhost" +btc_rpc_cookie = "~/.bitcoin/.cookie" btc_rpc_port = 8332 # Flags diff --git a/teos/src/config.rs b/teos/src/config.rs index 5436725..109dd91 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -41,6 +41,14 @@ impl std::fmt::Display for ConfigError { impl std::error::Error for ConfigError {} +#[derive(PartialEq)] +pub enum AuthMethod { + UserPass, + CookieFile, + Multiple, + Invalid, +} + /// Holds all the command line options. #[derive(StructOpt, Debug, Clone)] #[structopt(rename_all = "lowercase")] @@ -66,14 +74,18 @@ pub struct Opt { #[structopt(long)] pub btc_network: Option, - /// bitcoind rpcuser [default: user] + /// bitcoind rpcuser #[structopt(long)] pub btc_rpc_user: Option, - /// bitcoind rpcpassword [default: passwd] + /// bitcoind rpcpassword #[structopt(long)] pub btc_rpc_password: Option, + /// bitcoind rpccookie + #[structopt(long)] + pub btc_rpc_cookie: Option, + /// bitcoind rpcconnect [default: localhost] #[structopt(long)] pub btc_rpc_connect: Option, @@ -136,6 +148,7 @@ pub struct Config { // Bitcoind pub btc_network: String, pub btc_rpc_user: String, + pub btc_rpc_cookie: String, pub btc_rpc_password: String, pub btc_rpc_connect: String, pub btc_rpc_port: u16, @@ -164,6 +177,24 @@ pub struct Config { } impl Config { + /// The only combinations of valid authentication methods are: + /// - User **AND** password + /// - **OR** Cookie file + // + /// Any other combination will be rejected + pub fn get_auth_method(&self) -> AuthMethod { + match ( + self.btc_rpc_user.is_empty(), + self.btc_rpc_password.is_empty(), + self.btc_rpc_cookie.is_empty(), + ) { + (false, false, true) => AuthMethod::UserPass, + (true, true, false) => AuthMethod::CookieFile, + (true, true, true) => AuthMethod::Invalid, + _ => AuthMethod::Multiple, + } + } + /// Patches the configuration options with the command line options. pub fn patch_with_options(&mut self, options: Opt) { if options.api_bind.is_some() { @@ -187,6 +218,9 @@ impl Config { if options.btc_rpc_password.is_some() { self.btc_rpc_password = options.btc_rpc_password.unwrap(); } + if options.btc_rpc_cookie.is_some() { + self.btc_rpc_cookie = options.btc_rpc_cookie.unwrap(); + } if options.btc_rpc_connect.is_some() { self.btc_rpc_connect = options.btc_rpc_connect.unwrap(); } @@ -216,11 +250,14 @@ impl Config { /// This will also assign the default `btc_rpc_port` depending on the network if it has not /// been overwritten at this point. pub fn verify(&mut self) -> Result<(), ConfigError> { - if self.btc_rpc_user == String::new() { - return Err(ConfigError("btc_rpc_user must be set".to_owned())); - } - if self.btc_rpc_password == String::new() { - return Err(ConfigError("btc_rpc_password must be set".to_owned())); + let auth_method = self.get_auth_method(); + if auth_method == AuthMethod::Invalid { + return Err(ConfigError("No valid bitcoind auth provided. Set either both btc_rpc_user/btc_rpc_password or btc_rpc_cookie".to_owned())); + } else if auth_method == AuthMethod::Multiple { + return Err(ConfigError( + "Multiple bitcoind auth provided. Pick a single one (either btc_rpc_user/btc_rpc_password or btc_rpc_cookie)" + .to_owned(), + )); } // Normalize the network option to the ones used by bitcoind. @@ -291,6 +328,7 @@ impl Default for Config { btc_network: "mainnet".into(), btc_rpc_user: String::new(), btc_rpc_password: String::new(), + btc_rpc_cookie: String::new(), btc_rpc_connect: "localhost".into(), btc_rpc_port: 0, @@ -326,6 +364,7 @@ mod tests { btc_network: None, btc_rpc_user: None, btc_rpc_password: None, + btc_rpc_cookie: None, btc_rpc_connect: None, btc_rpc_port: None, data_dir: String::from("~/.teos"), @@ -363,7 +402,7 @@ mod tests { // required to be updated by the user. let mut config = Config::default(); assert!( - matches!(config.verify(), Err(ConfigError(e)) if e.contains("btc_rpc_user must be set")) + matches!(config.verify(), Err(ConfigError(e)) if e.contains("No valid bitcoind auth provided")) ); } diff --git a/teos/src/main.rs b/teos/src/main.rs index bdd30e0..8cc53c0 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -23,7 +23,7 @@ use teos::api::{http, tor::TorAPI}; use teos::bitcoin_cli::BitcoindClient; use teos::carrier::Carrier; use teos::chain_monitor::ChainMonitor; -use teos::config::{self, Config, Opt}; +use teos::config::{self, AuthMethod, Config, Opt}; use teos::dbm::DBM; use teos::gatekeeper::Gatekeeper; use teos::protos as msgs; @@ -142,12 +142,20 @@ async fn main() { }; log::info!("tower_id: {tower_pk}"); + let btc_rpc_auth = match conf.get_auth_method() { + AuthMethod::CookieFile => { + Auth::CookieFile(config::data_dir_absolute_path(conf.btc_rpc_cookie)) + } + AuthMethod::UserPass => Auth::UserPass(conf.btc_rpc_user, conf.btc_rpc_password), + // Notice an invalid conf would have failed on `Config::verify()` + _ => unreachable!("A verified conf will only have one of these two auth methods"), + }; + // Initialize our bitcoind client let (bitcoin_cli, bitcoind_reachable) = match BitcoindClient::new( &conf.btc_rpc_connect, conf.btc_rpc_port, - &conf.btc_rpc_user, - &conf.btc_rpc_password, + btc_rpc_auth.clone(), &conf.btc_network, ) .await @@ -176,7 +184,7 @@ async fn main() { let rpc = Arc::new( Client::new( &format!("{schema}{}:{}", conf.btc_rpc_connect, conf.btc_rpc_port), - Auth::UserPass(conf.btc_rpc_user.clone(), conf.btc_rpc_password.clone()), + btc_rpc_auth, ) .unwrap(), ); From 95f1c4c21d1738fefc135226ee80017051693e38 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 18 Oct 2023 12:54:54 -0400 Subject: [PATCH 106/119] Fixes cryptography tests utils --- teos-common/src/cryptography.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index 91e53ae..cf5fef3 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -81,17 +81,13 @@ pub fn decrypt(encrypted_blob: &[u8], secret: &Txid) -> Result Vec { let mut rng = rand::thread_rng(); let uniform_u8 = Uniform::new(u8::MIN, u8::MAX); - let v: Vec = (&mut rng).sample_iter(uniform_u8).take(size).collect(); - - v + (&mut rng).sample_iter(uniform_u8).take(size).collect() } /// Gets a key pair generated in a pseudorandom way. pub fn get_random_keypair() -> (SecretKey, PublicKey) { - let raw_sk = get_random_bytes(32); - loop { - if let Ok(sk) = SecretKey::from_slice(&raw_sk) { + if let Ok(sk) = SecretKey::from_slice(&get_random_bytes(32)) { return (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)); } } From f6decde5ff364cccfdc7b5ba7c9e20a38fadd30d Mon Sep 17 00:00:00 2001 From: daywalker90 <8257956+daywalker90@users.noreply.github.com> Date: Sat, 8 Jun 2024 12:16:34 +0200 Subject: [PATCH 107/119] modernize cln tests and CI --- .github/workflows/build.yaml | 6 +++++- .github/workflows/cln-plugin.yaml | 16 ++++++++++------ watchtower-plugin/src/retrier.rs | 4 ++-- watchtower-plugin/tests/conftest.py | 15 +-------------- watchtower-plugin/tests/pyproject.toml | 4 ++-- watchtower-plugin/tests/test.py | 24 +++++++++++++----------- 6 files changed, 33 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5eba801..2780156 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,6 +1,10 @@ name: Continuous Integration Checks -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: jobs: build: diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index b09064d..4933975 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -1,10 +1,14 @@ name: CI tests for CLN watchtower-plugin -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: env: - bitcoind_version: 0.20.1 - cln_version: 0.12.1 + bitcoind_version: "27.0" + cln_version: "24.02.2" jobs: cache-cln: @@ -28,10 +32,10 @@ jobs: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }} run: | - sudo apt-get update && sudo apt-get install gettext + sudo apt-get update && sudo apt-get install -y gettext protobuf-compiler git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }} pip install --user poetry && poetry install - ./configure --enable-developer && poetry run make + ./configure && poetry run make cln-plugin: needs: cache-cln @@ -69,4 +73,4 @@ jobs: - name: Run tests run: | cd watchtower-plugin/tests - DEVELOPER=1 SLOW_MACHINE=1 poetry run pytest test.py --log-cli-level=INFO -s + VALGRIND=0 SLOW_MACHINE=1 poetry run pytest test.py --log-cli-level=INFO -s diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index 0920e36..eaf79e6 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -103,7 +103,7 @@ impl RetryManager { } else if let Some(retrier) = self.retriers.get(&tower_id) { if retrier.is_idle() { if !data.is_none() { - log::error!("Data was send to an idle retier. This should have never happened. Please report! ({data:?})"); + log::error!("Data was send to an idle retrier. This should have never happened. Please report! ({data:?})"); continue; } log::info!( @@ -774,7 +774,7 @@ mod tests { .unwrap() .is_running()); - // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, so the retiers will be idle). + // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, so the retriers will be idle). wait_until!(wt_client .lock() .unwrap() diff --git a/watchtower-plugin/tests/conftest.py b/watchtower-plugin/tests/conftest.py index eb03b75..7a7be94 100644 --- a/watchtower-plugin/tests/conftest.py +++ b/watchtower-plugin/tests/conftest.py @@ -2,7 +2,7 @@ from pathlib import Path import subprocess from pyln.testing.fixtures import * # noqa: F401,F403 -from pyln.testing.utils import DEVELOPER, BITCOIND_CONFIG, TailableProc +from pyln.testing.utils import BITCOIND_CONFIG, TailableProc WT_PLUGIN = Path("~/.cargo/bin/watchtower-client").expanduser() TEOSD_CONFIG = { @@ -116,19 +116,6 @@ def pytest_runtest_makereport(item, call): setattr(item, "rep_" + rep.when, rep) -def pytest_configure(config): - config.addinivalue_line("markers", "developer: only run when developer is flagged on") - - -def pytest_runtest_setup(item): - for mark in item.iter_markers(name="developer"): - if not DEVELOPER: - if len(mark.args): - pytest.skip("!DEVELOPER: {}".format(mark.args[0])) - else: - pytest.skip("!DEVELOPER: Requires DEVELOPER=1") - - @pytest.fixture(scope="function", autouse=True) def log_name(request): # Here logging is used, you can use whatever you want to use for logs diff --git a/watchtower-plugin/tests/pyproject.toml b/watchtower-plugin/tests/pyproject.toml index abcb2e4..f11efae 100644 --- a/watchtower-plugin/tests/pyproject.toml +++ b/watchtower-plugin/tests/pyproject.toml @@ -12,8 +12,8 @@ black = "^22.6.0" [tool.poetry.dev-dependencies] pytest = "^7.1.2" pytest-timeout = "^2.1.0" -pyln-testing = "^0.12.1" -pyln-client = "^23.11" +pyln-testing = "^24.2.1" +pyln-client = "^24.2.1" [build-system] diff --git a/watchtower-plugin/tests/test.py b/watchtower-plugin/tests/test.py index 2066d1b..7b4657c 100644 --- a/watchtower-plugin/tests/test.py +++ b/watchtower-plugin/tests/test.py @@ -1,5 +1,4 @@ import pytest -from pyln.client import RpcError from conftest import WT_PLUGIN @@ -16,7 +15,6 @@ def change_endianness(x): return b[::-1].hex() -@pytest.mark.developer("Requires dev_sign_last_tx") def test_watchtower(node_factory, bitcoind, teosd): """ Test watchtower hook. @@ -27,7 +25,13 @@ def test_watchtower(node_factory, bitcoind, teosd): commitment transaction. """ - l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": WT_PLUGIN}]) + l1, l2 = node_factory.line_graph( + 2, + opts=[ + {"broken_log": r"Could not find resolution for output [0-9]?: did \*we\* cheat\?"}, + {"plugin": WT_PLUGIN}, + ], + ) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] @@ -60,7 +64,7 @@ def test_watchtower(node_factory, bitcoind, teosd): penalty_txid = bitcoind.rpc.getrawmempool()[0] # The channel still exists between the two peers, but it's on chain - assert l1.rpc.listpeers()["peers"][0]["channels"][0]["state"] == "ONCHAIN" + assert l1.rpc.listpeerchannels()["channels"][0]["state"] == "ONCHAIN" assert l2.rpc.getappointment(tower_id, locator)["status"] == "dispute_responded" # Generate blocks until the penalty gets irrevocably resolved @@ -90,7 +94,6 @@ def test_unreachable_watchtower(node_factory, bitcoind, teosd): {}, { "plugin": WT_PLUGIN, - "allow_broken_log": True, "dev-watchtower-max-retry-interval": max_interval_time, }, ], @@ -123,7 +126,7 @@ def test_auto_retry_watchtower(node_factory, bitcoind, teosd): {}, { "plugin": WT_PLUGIN, - "allow_broken_log": True, + "broken_log": r"plugin-watchtower-client: Data was send to an idle retrier. This should have never happened. Please report!.*", "watchtower-max-retry-time": 1, "watchtower-auto-retry-delay": 1, }, @@ -141,7 +144,7 @@ def test_auto_retry_watchtower(node_factory, bitcoind, teosd): l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) # Wait until the tower has been flagged as unreachable - l2.daemon.wait_for_log(f"Starting to idle") + l2.daemon.wait_for_log("Starting to idle") assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] @@ -161,7 +164,6 @@ def test_manually_retry_watchtower(node_factory, bitcoind, teosd): {}, { "plugin": WT_PLUGIN, - "allow_broken_log": True, "watchtower-max-retry-time": 0, }, ], @@ -178,7 +180,7 @@ def test_manually_retry_watchtower(node_factory, bitcoind, teosd): l1.rpc.pay(l2.rpc.invoice(25000000, "lbl1", "desc1")["bolt11"]) # Wait until the tower has been flagged as unreachable - l2.daemon.wait_for_log(f"Starting to idle") + l2.daemon.wait_for_log("Starting to idle") assert l2.rpc.gettowerinfo(tower_id)["status"] == "unreachable" assert l2.rpc.gettowerinfo(tower_id)["pending_appointments"] @@ -193,7 +195,7 @@ def test_manually_retry_watchtower(node_factory, bitcoind, teosd): def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): - l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": WT_PLUGIN, "allow_broken_log": True}]) + l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": WT_PLUGIN}]) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] @@ -210,7 +212,7 @@ def test_misbehaving_watchtower(node_factory, bitcoind, teosd, directory): def test_get_appointment(node_factory, bitcoind, teosd, directory): - l1, l2 = node_factory.line_graph(2, opts=[{"allow_broken_log": True}, {"plugin": WT_PLUGIN}]) + l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": WT_PLUGIN}]) # We need to register l2 with the tower tower_id = teosd.cli.gettowerinfo()["tower_id"] From d30fc7d1213177978ffb2ebd29765dba929af3d4 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 29 Jul 2024 13:08:57 -0400 Subject: [PATCH 108/119] Fixes clippy issues --- teos-common/src/receipts.rs | 8 ++++---- teos/src/config.rs | 13 +++++-------- teos/src/tx_index.rs | 2 +- watchtower-plugin/src/main.rs | 2 +- watchtower-plugin/src/retrier.rs | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 6e806c2..699c3c5 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -9,11 +9,11 @@ use crate::{cryptography, UserId}; /// Proof that a user has registered with a tower. This serves two purposes: /// /// - First, the user is able to prove that the tower agreed on providing a service. If a tower refuses to accept appointments -/// from a user (claiming the subscription has expired) but the expiry time has still not passed and the tower cannot -/// provide the relevant appointments signed by the user, it means it is cheating. +/// from a user (claiming the subscription has expired) but the expiry time has still not passed and the tower cannot +/// provide the relevant appointments signed by the user, it means it is cheating. /// - Second, it serves as proof, alongside an appointment receipt, that an appointment was not fulfilled. A registration receipt -/// specifies a subscription period (`subscription_start` - `subscription_expiry`) and the appointment a `start_block` so inclusion -/// can be proved. +/// specifies a subscription period (`subscription_start` - `subscription_expiry`) and the appointment a `start_block` so inclusion +/// can be proved. /// /// TODO: / DISCUSS: In order to minimize the amount of receipts the user has to store, the tower could batch subscription receipts /// as long as the user info is still known. That is, if a user has a subscription with range (S, E) and the user renews the subscription diff --git a/teos/src/config.rs b/teos/src/config.rs index 109dd91..f1db3de 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -18,13 +18,10 @@ pub fn data_dir_absolute_path(data_dir: String) -> PathBuf { pub fn from_file(path: &PathBuf) -> T { match std::fs::read(path) { - Ok(file_content) => toml::from_slice::(&file_content).map_or_else( - |e| { - eprintln!("Couldn't parse config file: {e}"); - T::default() - }, - |config| config, - ), + Ok(file_content) => toml::from_slice::(&file_content).unwrap_or_else(|e| { + eprintln!("Couldn't parse config file: {e}"); + T::default() + }), Err(_) => T::default(), } } @@ -392,7 +389,7 @@ mod tests { assert_eq!(config.api_bind, expected_value); // Check the rest of fields are equal. The easiest is to just the field back and compare with a clone - config.api_bind = config_clone.api_bind.clone(); + config.api_bind.clone_from(&config_clone.api_bind); assert_eq!(config, config_clone); } diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index c9d22b4..404c047 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -378,7 +378,7 @@ mod tests { // Check that the block data is not in the cache anymore assert_eq!(cache.blocks().len(), cache.size - i - 1); assert!(!cache.blocks().contains(&header.block_hash())); - assert!(cache.tx_in_block.get(&header.block_hash()).is_none()); + assert!(!cache.tx_in_block.contains_key(&header.block_hash())); for locator in locators.iter() { assert!(!cache.contains_key(locator)); } diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 5778e8d..1b845a6 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -385,7 +385,7 @@ async fn abandon_tower( ) -> Result { let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; let mut state = plugin.state().lock().unwrap(); - if state.towers.get(&tower_id).is_some() { + if state.towers.contains_key(&tower_id) { state.remove_tower(tower_id).unwrap(); Ok(json!(format!("{tower_id} successfully abandoned"))) } else { diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index eaf79e6..dcbfaa6 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -432,7 +432,7 @@ impl Retrier { // Create a new scope so we can get all the data only locking the WTClient once. let (tower_id, status, net_addr, user_id, user_sk, proxy) = { let wt_client = self.wt_client.lock().unwrap(); - if wt_client.towers.get(&self.tower_id).is_none() { + if !wt_client.towers.contains_key(&self.tower_id) { return Err(Error::permanent(RetryError::Abandoned)); } From bb2df6a700f76dd4651a3b9af8748afd881136ca Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 29 Dec 2023 11:46:33 -0500 Subject: [PATCH 109/119] Bumps rcgen to version 0.13.1 and updates tls.rs accordingly rcgen was using version 0.8 which used a ring version that didn't support ppc64le arch. Support for it was recently added in ring=0.17.0 and supported by rcgen in 0.13 --- Cargo.lock | 869 +++++++++++++++++++++++++++++++++--------------- teos/Cargo.toml | 2 +- teos/src/cli.rs | 2 +- teos/src/tls.rs | 37 +-- 4 files changed, 630 insertions(+), 280 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89eb349..91b28bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -17,7 +32,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.11", "once_cell", "version_check", ] @@ -31,6 +46,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -46,6 +70,45 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +[[package]] +name = "asn1-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", + "synstructure 0.13.1", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -74,18 +137,18 @@ checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -112,11 +175,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.5", + "getrandom 0.2.11", "instant", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "rand 0.8.5", - "tokio 1.25.0", + "tokio 1.36.0", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] [[package]] @@ -264,9 +342,12 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -282,21 +363,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.7.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08493fa7707effc63254c66c6ea908675912493cd67952eda23c09fae2610b1" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.1.5", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6547abe025f4027edacd9edaa357aded014eecec42a5070d9b885c3c334aba2" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" dependencies = [ "aead", "chacha20", @@ -305,16 +386,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "chunked_transfer" version = "1.4.0" @@ -358,9 +429,9 @@ dependencies = [ "log", "serde", "serde_json", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-stream", - "tokio-util 0.7.0", + "tokio-util 0.7.10", ] [[package]] @@ -396,6 +467,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.1" @@ -417,9 +497,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array", "subtle", @@ -427,9 +507,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -454,7 +534,7 @@ dependencies = [ "deadpool-runtime", "num_cpus", "retain_mut", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] @@ -463,30 +543,29 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" -[[package]] -name = "der-oid-macro" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" -dependencies = [ - "num-bigint", - "num-traits", - "syn", -] - [[package]] name = "der-parser" -version = "6.0.1" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cddf120f700b411b2b02ebeb7f04dc0b7c8835909a6c2f52bf72ed0dd3433b2" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "der-oid-macro", + "asn1-rs", + "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -497,7 +576,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.98", ] [[package]] @@ -519,6 +598,17 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "ed25519" version = "1.5.2" @@ -570,6 +660,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.2.8" @@ -727,7 +823,7 @@ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -755,7 +851,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "pin-utils", "slab", ] @@ -783,22 +879,28 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "globset" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.18", "bstr", "fnv", "log", @@ -817,7 +919,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.8.0", "slab", "tokio 0.2.25", "tokio-util 0.3.1", @@ -827,9 +929,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes 1.1.0", "fnv", @@ -837,10 +939,10 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.2.5", "slab", - "tokio 1.25.0", - "tokio-util 0.6.9", + "tokio 1.36.0", + "tokio-util 0.7.10", "tracing", ] @@ -853,13 +955,19 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "hashlink" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" dependencies = [ - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -944,13 +1052,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes 1.1.0", "fnv", - "itoa 1.0.1", + "itoa 1.0.10", ] [[package]] @@ -965,20 +1073,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", ] [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1024,23 +1132,23 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes 1.1.0", "futures-channel", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.25", "http", - "http-body 0.4.4", + "http-body 0.4.6", "httparse", "httpdate 1.0.2", - "itoa 1.0.1", - "pin-project-lite 0.2.8", - "socket2 0.4.4", - "tokio 1.25.0", + "itoa 1.0.10", + "pin-project-lite 0.2.13", + "socket2 0.5.6", + "tokio 1.36.0", "tower-service", "tracing", "want", @@ -1052,9 +1160,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.18", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "hyper 0.14.28", + "pin-project-lite 0.2.13", + "tokio 1.36.0", "tokio-io-timeout", ] @@ -1065,9 +1173,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.1.0", - "hyper 0.14.18", + "hyper 0.14.28", "native-tls", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-native-tls", ] @@ -1089,7 +1197,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.11.2", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -1155,9 +1273,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" @@ -1252,9 +1370,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libsqlite3-sys" @@ -1298,7 +1416,7 @@ checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" dependencies = [ "bitcoin", "lightning", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] @@ -1318,12 +1436,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matches" @@ -1333,9 +1448,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -1359,6 +1474,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.6.23" @@ -1380,14 +1504,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.48.0", ] [[package]] @@ -1413,7 +1536,7 @@ dependencies = [ "colored", "deadpool", "futures", - "hyper 0.14.18", + "hyper 0.14.28", "lazy_static", "log", "rand 0.8.5", @@ -1421,7 +1544,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "similar", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] @@ -1498,6 +1621,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -1537,19 +1666,28 @@ dependencies = [ ] [[package]] -name = "oid-registry" -version = "0.2.0" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "der-parser", + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +dependencies = [ + "asn1-rs", ] [[package]] name = "once_cell" -version = "1.9.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -1580,7 +1718,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -1652,11 +1790,12 @@ dependencies = [ [[package]] name = "pem" -version = "1.1.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.13.0", + "base64 0.21.2", + "serde", ] [[package]] @@ -1672,27 +1811,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.8.0", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -1703,9 +1842,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1725,11 +1864,17 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.1", "opaque-debug", "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1745,7 +1890,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.98", "version_check", ] @@ -1762,9 +1907,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -1819,7 +1964,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -1832,7 +1977,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -1847,9 +1992,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.15" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1941,7 +2086,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.11", ] [[package]] @@ -1955,13 +2100,14 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.8.14" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7" +checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779" dependencies = [ - "chrono", "pem", - "ring", + "ring 0.17.7", + "rustls-pki-types", + "time", "x509-parser", "yasna", ] @@ -1986,20 +2132,32 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ - "aho-corasick", + "aho-corasick 1.1.3", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick 1.1.3", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "remove_dir_all" @@ -2021,10 +2179,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.25", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-tls", "ipnet", "js-sys", @@ -2033,11 +2191,11 @@ dependencies = [ "mime", "native-tls", "percent-encoding", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "serde", "serde_json", "serde_urlencoded", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2064,11 +2222,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi 0.3.9", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.11", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "rusqlite" version = "0.26.3" @@ -2084,6 +2256,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2124,7 +2302,7 @@ checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", "log", - "ring", + "ring 0.16.20", "sct", "webpki", ] @@ -2138,6 +2316,12 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "ryu" version = "1.0.9" @@ -2172,8 +2356,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -2226,22 +2410,22 @@ checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -2250,8 +2434,8 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "indexmap", - "itoa 1.0.1", + "indexmap 1.8.0", + "itoa 1.0.10", "ryu", "serde", ] @@ -2263,7 +2447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.10", "ryu", "serde", ] @@ -2275,7 +2459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.1", "digest 0.10.7", ] @@ -2286,7 +2470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.1", "digest 0.10.7", ] @@ -2298,7 +2482,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.1", "digest 0.9.0", "opaque-debug", ] @@ -2374,12 +2558,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "winapi 0.3.9", + "windows-sys 0.52.0", ] [[package]] @@ -2421,14 +2605,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -2441,6 +2625,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -2449,10 +2644,21 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -2500,7 +2706,7 @@ dependencies = [ "structopt", "tempdir", "teos-common", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-stream", "toml", "tonic 0.6.2", @@ -2562,26 +2768,41 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", ] [[package]] name = "time" -version = "0.3.7" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "itoa 1.0.1", + "deranged", + "itoa 1.0.10", "libc", + "num-conv", "num_threads", + "powerfmt", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.3" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tinyvec" @@ -2618,22 +2839,21 @@ dependencies = [ [[package]] name = "tokio" -version = "1.25.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ - "autocfg", + "backtrace", "bytes 1.1.0", "libc", - "memchr", - "mio 0.8.4", + "mio 0.8.11", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.5.6", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -2642,19 +2862,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.36.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] @@ -2664,7 +2884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] @@ -2674,7 +2894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.25.0", + "tokio 1.36.0", "webpki", ] @@ -2687,7 +2907,7 @@ dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] @@ -2697,8 +2917,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.36.0", ] [[package]] @@ -2709,7 +2929,7 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "tokio 1.25.0", + "tokio 1.36.0", "tungstenite", ] @@ -2729,30 +2949,30 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.36.0", ] [[package]] name = "tokio-util" -version = "0.7.0" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "log", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.36.0", + "tracing", ] [[package]] @@ -2776,19 +2996,19 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.25", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-timeout", "percent-encoding", "pin-project", "prost 0.8.0", "prost-derive 0.8.0", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2808,19 +3028,19 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.25", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-timeout", "percent-encoding", "pin-project", "prost 0.9.0", "prost-derive 0.9.0", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2837,7 +3057,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.98", ] [[package]] @@ -2857,24 +3077,24 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "tokio 1.25.0", + "tokio 1.36.0", ] [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.8.0", "pin-project", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "rand 0.8.5", "slab", - "tokio 1.25.0", - "tokio-util 0.7.0", + "tokio 1.36.0", + "tokio-util 0.7.10", "tower-layer", "tower-service", "tracing", @@ -2882,9 +3102,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -2894,35 +3114,34 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.43", ] [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -3022,9 +3241,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array", "subtle", @@ -3036,6 +3255,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.2.2" @@ -3093,7 +3318,7 @@ dependencies = [ "futures-util", "headers", "http", - "hyper 0.14.18", + "hyper 0.14.28", "log", "mime", "mime_guess", @@ -3105,10 +3330,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.25.0", + "tokio 1.36.0", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.7.0", + "tokio-util 0.7.10", "tower-service", "tracing", ] @@ -3119,12 +3344,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3152,7 +3371,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 1.0.98", "wasm-bindgen-shared", ] @@ -3186,7 +3405,7 @@ checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3214,7 +3433,7 @@ dependencies = [ "serde_json", "tempdir", "teos-common", - "tokio 1.25.0", + "tokio 1.36.0", "tonic 0.5.2", ] @@ -3234,8 +3453,8 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -3311,21 +3530,81 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -3338,6 +3617,18 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -3350,6 +3641,18 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -3362,6 +3665,18 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -3374,12 +3689,36 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -3392,6 +3731,18 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "winreg" version = "0.10.1" @@ -3413,36 +3764,36 @@ dependencies = [ [[package]] name = "x509-parser" -version = "0.12.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "base64 0.13.0", - "chrono", + "asn1-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", - "ring", + "ring 0.17.7", "rusticata-macros", "thiserror", + "time", ] [[package]] name = "yasna" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "chrono", + "time", ] [[package]] name = "zeroize" -version = "1.3.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -3455,6 +3806,6 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 1.0.98", + "synstructure 0.12.6", ] diff --git a/teos/Cargo.toml b/teos/Cargo.toml index f01ab5c..3d66c9d 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -20,7 +20,7 @@ hex = { version = "0.4.3", features = [ "serde" ] } home = "0.5.3" log = "0.4" prost = "0.9" -rcgen = { version = "0.8", features = ["pem", "x509-parser"] } +rcgen = { version = "0.13.1", features = ["pem", "x509-parser"] } rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" serde_json = "1.0" diff --git a/teos/src/cli.rs b/teos/src/cli.rs index 3ef1d9f..93ca300 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -53,7 +53,7 @@ async fn main() { .ca_certificate(ca_cert) .identity(Identity::from_pem(certificate, key)); - let channel = Channel::from_shared(format!("http://{}:{}", conf.rpc_bind, conf.rpc_port)) + let channel = Channel::from_shared(format!("https://{}:{}", conf.rpc_bind, conf.rpc_port)) .expect("Cannot create channel from endpoint") .tls_config(tls) .unwrap_or_else(|e| { diff --git a/teos/src/tls.rs b/teos/src/tls.rs index d31783e..33e3354 100644 --- a/teos/src/tls.rs +++ b/teos/src/tls.rs @@ -5,7 +5,7 @@ * https://github.com/ElementsProject/lightning/blob/master/LICENSE */ -use rcgen::{Certificate, KeyPair, RcgenError}; +use rcgen::{Certificate, Error as RcgenError, KeyPair}; use std::convert::TryFrom; use std::path::Path; @@ -30,21 +30,20 @@ impl From for GenCertificateFailure { /// Just a wrapper around a certificate and an associated keypair. #[derive(Clone, Debug)] -pub struct Identity { +struct Identity { pub key: Vec, pub certificate: Vec, } -impl TryFrom<&Identity> for Certificate { +impl TryFrom<&Identity> for (Certificate, KeyPair) { type Error = RcgenError; - fn try_from(id: &Identity) -> Result { - let keystr = String::from_utf8_lossy(&id.key); - let key = KeyPair::from_pem(&keystr)?; - let certstr = String::from_utf8_lossy(&id.certificate); - let params = rcgen::CertificateParams::from_ca_cert_pem(&certstr, key)?; - let cert = Certificate::from_params(params)?; - Ok(cert) + fn try_from(id: &Identity) -> Result<(Certificate, KeyPair), RcgenError> { + let key = KeyPair::from_pem(&String::from_utf8_lossy(&id.key))?; + let params = + rcgen::CertificateParams::from_ca_cert_pem(&String::from_utf8_lossy(&id.certificate))?; + let cert = params.self_signed(&key)?; + Ok((cert, key)) } } @@ -72,30 +71,30 @@ fn generate_or_load_identity( // Did we have to generate a new key? In that case we also need to regenerate the certificate. if !key_path.exists() || !cert_path.exists() { log::debug!("Generating a new keypair in {key_path:?}, it didn't exist",); - let keypair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?; + let keypair = KeyPair::generate()?; std::fs::write(&key_path, keypair.serialize_pem())?; log::debug!("Generating a new certificate for key {key_path:?} at {cert_path:?}",); // Configure the certificate we want. - let subject_alt_names = vec!["cln".to_string(), "localhost".to_string()]; - let mut params = rcgen::CertificateParams::new(subject_alt_names); - params.key_pair = Some(keypair); - params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; + let subject_alt_names = vec!["teos".to_string(), "localhost".to_string()]; + let mut params = rcgen::CertificateParams::new(subject_alt_names)?; if parent.is_none() { params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); } else { - params.is_ca = rcgen::IsCa::SelfSignedOnly; + params.is_ca = rcgen::IsCa::NoCa; } params .distinguished_name .push(rcgen::DnType::CommonName, name); - let cert = Certificate::from_params(params)?; std::fs::write( &cert_path, match parent { - None => cert.serialize_pem()?, - Some(ca) => cert.serialize_pem_with_signer(&Certificate::try_from(ca)?)?, + None => params.self_signed(&keypair)?.pem(), + Some(ca) => { + let (ca_cert, ca_key) = <(Certificate, KeyPair)>::try_from(ca)?; + params.signed_by(&keypair, &ca_cert, &ca_key)?.pem() + } }, )?; } From 81a659c0f628a5017d169636479cffc0d297ba15 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 20 Mar 2024 14:46:37 -0400 Subject: [PATCH 110/119] Bumps tonic to 0.11 and prost to 0.12, adapts Cargo files accordingly Bumping tonic required cargo file edition to be bumped to 2021 --- .github/workflows/build.yaml | 8 + Cargo.lock | 313 +++++++++++++++++------------------ Cargo.toml | 1 + teos-common/Cargo.toml | 8 +- teos/Cargo.toml | 8 +- watchtower-plugin/Cargo.toml | 4 +- 6 files changed, 168 insertions(+), 174 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2780156..88768c1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -25,6 +25,10 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Build on Rust ${{ matrix.toolchain }} run: | cargo build ${{ matrix.arguments }} --verbose --color always @@ -42,6 +46,10 @@ jobs: with: toolchain: stable components: rustfmt, clippy + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Run rustfmt run: | cargo fmt --verbose --check -- --color always diff --git a/Cargo.lock b/Cargo.lock index 91b28bd..a8ace64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes 1.1.0", + "futures-util", + "http", + "http-body 0.4.6", + "hyper 0.14.28", + "itoa 1.0.10", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite 0.2.13", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes 1.1.0", + "futures-util", + "http", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backoff" version = "0.4.0" @@ -1004,6 +1049,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1446,6 +1497,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.1" @@ -1561,7 +1618,7 @@ dependencies = [ "log", "memchr", "mime", - "spin 0.9.8", + "spin", "version_check", ] @@ -1881,6 +1938,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.43", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1916,78 +1983,56 @@ dependencies = [ [[package]] name = "prost" -version = "0.8.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes 1.1.0", - "prost-derive 0.8.0", -] - -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.9.0", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.9.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes 1.1.0", - "heck", + "heck 0.4.1", "itertools", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", - "prost 0.9.0", + "prettyplease", + "prost", "prost-types", "regex", + "syn 2.0.43", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.8.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 1.0.98", -] - -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 1.0.98", + "syn 2.0.43", ] [[package]] name = "prost-types" -version = "0.9.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "bytes 1.1.0", - "prost 0.9.0", + "prost", ] [[package]] @@ -2105,7 +2150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779" dependencies = [ "pem", - "ring 0.17.7", + "ring", "rustls-pki-types", "time", "x509-parser", @@ -2212,21 +2257,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi 0.3.9", -] - [[package]] name = "ring" version = "0.17.7" @@ -2236,8 +2266,8 @@ dependencies = [ "cc", "getrandom 0.2.11", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.48.0", ] @@ -2296,15 +2326,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ - "base64 0.13.0", "log", - "ring 0.16.20", - "sct", - "webpki", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] @@ -2316,12 +2347,39 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +dependencies = [ + "base64 0.21.2", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.9" @@ -2350,16 +2408,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - [[package]] name = "secp256k1" version = "0.22.1" @@ -2566,12 +2614,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -2601,7 +2643,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", @@ -2636,6 +2678,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -2696,7 +2744,7 @@ dependencies = [ "lightning-block-sync", "lightning-net-tokio", "log", - "prost 0.9.0", + "prost", "rand 0.8.5", "rcgen", "rusqlite", @@ -2709,7 +2757,7 @@ dependencies = [ "tokio 1.36.0", "tokio-stream", "toml", - "tonic 0.6.2", + "tonic", "tonic-build", "torut", "triggered", @@ -2724,12 +2772,12 @@ dependencies = [ "chacha20poly1305", "hex", "lightning", - "prost 0.9.0", + "prost", "rand 0.8.5", "rusqlite", "serde", "serde_json", - "tonic 0.6.2", + "tonic", "tonic-build", ] @@ -2889,13 +2937,13 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio 1.36.0", - "webpki", ] [[package]] @@ -2947,20 +2995,6 @@ dependencies = [ "tokio 0.2.25", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes 1.1.0", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.2.13", - "tokio 1.36.0", -] - [[package]] name = "tokio-util" version = "0.7.10" @@ -2986,16 +3020,15 @@ dependencies = [ [[package]] name = "tonic" -version = "0.5.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796c5e1cd49905e65dd8e700d4cb1dffcbfdb4fc9d017de08c1a537afd83627c" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" dependencies = [ "async-stream", "async-trait", - "base64 0.13.0", + "axum", + "base64 0.21.2", "bytes 1.1.0", - "futures-core", - "futures-util", "h2 0.3.25", "http", "http-body 0.4.6", @@ -3003,61 +3036,29 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.8.0", - "prost-derive 0.8.0", + "prost", + "rustls-pemfile 2.1.1", + "rustls-pki-types", "tokio 1.36.0", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", "tracing", - "tracing-futures", -] - -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64 0.13.0", - "bytes 1.1.0", - "futures-core", - "futures-util", - "h2 0.3.25", - "http", - "http-body 0.4.6", - "hyper 0.14.28", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost 0.9.0", - "prost-derive 0.9.0", - "tokio 1.36.0", - "tokio-rustls", - "tokio-stream", - "tokio-util 0.6.10", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", ] [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", - "syn 1.0.98", + "syn 2.0.43", ] [[package]] @@ -3249,12 +3250,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3325,7 +3320,7 @@ dependencies = [ "multer", "percent-encoding", "pin-project", - "rustls-pemfile", + "rustls-pemfile 1.0.3", "scoped-tls", "serde", "serde_json", @@ -3434,7 +3429,7 @@ dependencies = [ "tempdir", "teos-common", "tokio 1.36.0", - "tonic 0.5.2", + "tonic", ] [[package]] @@ -3447,16 +3442,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - [[package]] name = "which" version = "4.2.4" @@ -3774,7 +3759,7 @@ dependencies = [ "lazy_static", "nom", "oid-registry", - "ring 0.17.7", + "ring", "rusticata-macros", "thiserror", "time", diff --git a/Cargo.toml b/Cargo.toml index 3a62379..76af6ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "teos", diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index be0cb48..1776689 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -2,18 +2,18 @@ name = "teos-common" version = "0.2.0" authors = ["Sergi Delgado Segura "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] # General hex = { version = "0.4.3", features = [ "serde" ] } -prost = "0.9" +prost = "0.12" rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" serde_json = "1.0" -tonic = "0.6" +tonic = "0.11" # Crypto rand = "0.8.4" @@ -24,4 +24,4 @@ bitcoin = { version = "0.28.0", features = [ "use-serde" ] } lightning = "0.0.108" [build-dependencies] -tonic-build = "0.6" \ No newline at end of file +tonic-build = "0.11" \ No newline at end of file diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 3d66c9d..0fe9339 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -3,7 +3,7 @@ name = "teos" version = "0.2.0" authors = ["Sergi Delgado Segura "] license = "MIT" -edition = "2018" +edition = "2021" default-run="teosd" [[bin]] @@ -19,7 +19,7 @@ path = "src/main.rs" hex = { version = "0.4.3", features = [ "serde" ] } home = "0.5.3" log = "0.4" -prost = "0.9" +prost = "0.12" rcgen = { version = "0.13.1", features = ["pem", "x509-parser"] } rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" @@ -27,7 +27,7 @@ serde_json = "1.0" simple_logger = "2.1.0" structopt = "0.3" toml = "0.5" -tonic = { version = "0.6", features = [ "tls", "transport" ] } +tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread" ] } triggered = "0.1.2" warp = "0.3.5" @@ -44,7 +44,7 @@ lightning-block-sync = { version = "0.0.108", features = [ "rpc-client" ] } teos-common = { path = "../teos-common" } [build-dependencies] -tonic-build = "0.6" +tonic-build = "0.11" [dev-dependencies] jsonrpc-http-server = "17.1.0" diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 976f8e9..ed9aa64 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -3,7 +3,7 @@ name = "watchtower-plugin" version = "0.2.0" authors = ["Sergi Delgado Segura "] license = "MIT" -edition = "2018" +edition = "2021" [[bin]] name = "watchtower-client" @@ -21,7 +21,7 @@ log = "0.4.16" rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" serde_json = { version = "1.0", features = [ "preserve_order" ] } -tonic = { version = "^0.5", features = [ "tls", "transport" ] } +tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning From 2bf359582dd019411dfcdf3545545705a9da16c4 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 29 Jul 2024 16:17:37 -0400 Subject: [PATCH 111/119] Adds protoc GH actions to watchtower-plugin job --- .github/workflows/cln-plugin.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 4933975..dd150e8 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -1,6 +1,6 @@ name: CI tests for CLN watchtower-plugin -on: +on: push: branches: - master @@ -19,6 +19,9 @@ jobs: with: python-version: '3.9' check-latest: true + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Create CLN cache id: cache-cln uses: actions/cache@v4 @@ -28,11 +31,11 @@ jobs: path: lightning key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} - name: Compile CLN - env: + env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }} run: | - sudo apt-get update && sudo apt-get install -y gettext protobuf-compiler + sudo apt-get update && sudo apt-get install -y gettext git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }} pip install --user poetry && poetry install ./configure && poetry run make @@ -46,6 +49,9 @@ jobs: with: python-version: '3.9' check-latest: true + - uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install bitcoind run: | wget https://bitcoincore.org/bin/bitcoin-core-${{ env.bitcoind_version }}/bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz @@ -64,7 +70,7 @@ jobs: cd lightning && sudo make install - name: Install teos and the plugin run: | - cargo install --locked --path teos + cargo install --locked --path teos cargo install --locked --path watchtower-plugin - name: Add test dependencies run: | From 011ba645bb2e275d58539783182fd3d0bba1cab1 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Tue, 9 Apr 2024 11:51:24 +0200 Subject: [PATCH 112/119] meta: add possibility to install cln plugin with coffee Core lightning is starting to support a decent plugin manager that gives you the possibility to install a plugin without care about the process. Coffee [1] is a plugin manager that uses the manifest like all basic plugin managers (npm, pacman ...). So this commit is adding support for coffee and allowing people to install plugins with the following command: ``` coffee remote add teos-git https://github.com/talaia-labs/rust-teos.git coffee install rust-teos -v coffee list ``` [1] https://github.com/coffee-tools/coffee Signed-off-by: Vincenzo Palazzo --- coffee.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 coffee.yml diff --git a/coffee.yml b/coffee.yml new file mode 100644 index 0000000..143466e --- /dev/null +++ b/coffee.yml @@ -0,0 +1,10 @@ +--- +plugin: + name: rust-teos + version: 0.2.0 + lang: rust + install: | + cargo build --release --locked --package watchtower-plugin + cp target/release/watchtower-client . + cargo clean + main: watchtower-client From 168457180802e50a74756a81ea280e80095130b9 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 5 Sep 2024 16:48:44 -0400 Subject: [PATCH 113/119] Makes sure the time create is up to date Compiling time with rustc over 1.80.0 (inclusive) results in a crash for old versions of the time crate, which CLN uses. Make sure we are up to date with the current revision --- .github/workflows/cln-plugin.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index dd150e8..15b74ff 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -38,6 +38,7 @@ jobs: sudo apt-get update && sudo apt-get install -y gettext git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }} pip install --user poetry && poetry install + cargo update -p time ./configure && poetry run make cln-plugin: From cc0d18393ddc0fd80d30a47680e901c191a3001b Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 5 Sep 2024 17:17:39 -0400 Subject: [PATCH 114/119] plugin: replaces map_err for inspect_err in cases where we were only logging and returning --- watchtower-plugin/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 1b845a6..f7942cc 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -636,9 +636,8 @@ async fn main() -> Result<(), Error> { .as_i64() .unwrap(), ) - .map_err(|e| { + .inspect_err(|_| { log::error!("{} out of range", constants::WT_MAX_RETRY_TIME); - e })?; let auto_retry_delay = u32::try_from( @@ -648,9 +647,8 @@ async fn main() -> Result<(), Error> { .as_i64() .unwrap(), ) - .map_err(|e| { + .inspect_err(|_| { log::error!("{} out of range", constants::WT_AUTO_RETRY_DELAY); - e })?; let max_interval_time = u16::try_from( @@ -660,9 +658,8 @@ async fn main() -> Result<(), Error> { .as_i64() .unwrap(), ) - .map_err(|e| { + .inspect_err(|_| { log::error!("{} out of range", constants::DEV_WT_MAX_RETRY_INTERVAL); - e })?; let plugin = midstate.start(wt_client.clone()).await?; From b3a621a7819737bc3200706885cf9d00c4b4cdee Mon Sep 17 00:00:00 2001 From: dzdidi Date: Thu, 30 Jan 2025 13:50:57 +0100 Subject: [PATCH 115/119] Update dependencies - Add toolchain - Common: - bitcoin v0.32.0; - lightning v0.1.0 - TEOS: - bitcoin v0.32.0; - bitcoincore-rpc v0.19.0; - lightning v0.1.0; - lightning-net-tokio v0.1.0; - lightning-block-sync v0.1.0 - Watchtower-Plugin: - bitcoin v0.32.0; - cln-plugin v0.3.0 --- .github/workflows/cln-plugin.yaml | 14 +- Cargo.lock | 2188 ++++++++++++++++------------- Cargo.toml | 2 +- DEPENDENCIES.md | 2 +- rust-toolchain.toml | 6 + teos-common/Cargo.toml | 6 +- teos-common/src/cryptography.rs | 23 +- teos-common/src/receipts.rs | 6 +- teos-common/src/test_utils.rs | 14 +- teos/Cargo.toml | 11 +- teos/src/api/http.rs | 23 +- teos/src/api/internal.rs | 48 +- teos/src/bitcoin_cli.rs | 27 +- teos/src/carrier.rs | 39 +- teos/src/chain_monitor.rs | 8 +- teos/src/dbm.rs | 18 +- teos/src/extended_appointment.rs | 2 +- teos/src/gatekeeper.rs | 6 +- teos/src/main.rs | 15 +- teos/src/responder.rs | 63 +- teos/src/test_utils.rs | 62 +- teos/src/tx_index.rs | 112 +- teos/src/watcher.rs | 105 +- watchtower-plugin/Cargo.toml | 6 +- watchtower-plugin/src/main.rs | 96 +- watchtower-plugin/src/retrier.rs | 28 +- 26 files changed, 1649 insertions(+), 1281 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 15b74ff..6b710ab 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -8,7 +8,7 @@ on: env: bitcoind_version: "27.0" - cln_version: "24.02.2" + cln_version: "24.11.1" jobs: cache-cln: @@ -53,6 +53,11 @@ jobs: - uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.81.0 + components: rustfmt, clippy - name: Install bitcoind run: | wget https://bitcoincore.org/bin/bitcoin-core-${{ env.bitcoind_version }}/bitcoin-${{ env.bitcoind_version }}-x86_64-linux-gnu.tar.gz @@ -66,9 +71,10 @@ jobs: with: path: lightning key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} - - name: Link CLN - run: | - cd lightning && sudo make install + - name: Link CLN + run: | + source $HOME/.cargo/env + cd lightning && sudo make install - name: Install teos and the plugin run: | cargo install --locked --path teos diff --git a/Cargo.lock b/Cargo.lock index a8ace64..bbf6e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -28,24 +28,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.15", "once_cell", "version_check", ] -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -66,15 +57,21 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -88,14 +85,14 @@ dependencies = [ [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", - "synstructure 0.13.1", + "syn 2.0.96", + "synstructure", ] [[package]] @@ -106,7 +103,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -121,34 +118,35 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite 0.2.16", ] [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", ] [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -164,9 +162,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -176,18 +174,18 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags", - "bytes 1.1.0", + "bitflags 1.3.2", + "bytes 1.9.0", "futures-util", - "http", + "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", - "itoa 1.0.10", + "hyper 0.14.32", + "itoa 1.0.14", "matchit", "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", "rustversion", "serde", "sync_wrapper", @@ -203,9 +201,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.9.0", "futures-util", - "http", + "http 0.2.12", "http-body 0.4.6", "mime", "rustversion", @@ -220,26 +218,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.11", + "getrandom 0.2.15", "instant", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", "rand 0.8.5", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -249,59 +247,98 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] -name = "base64" -version = "0.13.0" +name = "base58ck" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - -[[package]] -name = "base64-compat" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "byteorder", + "bitcoin-internals", + "bitcoin_hashes", ] [[package]] -name = "bech32" -version = "0.8.1" +name = "base64" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bitcoin" -version = "0.28.1" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bba324e6baf655b882df672453dbbc527bc938cadd27750ae510aaccc3a66a" +checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ - "base64-compat", + "base58ck", "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", "bitcoin_hashes", + "hex-conservative", + "hex_lit", "secp256k1", "serde", ] [[package]] -name = "bitcoin_hashes" -version = "0.10.0" +name = "bitcoin-internals" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" dependencies = [ "serde", ] [[package]] -name = "bitcoincore-rpc" -version = "0.15.0" +name = "bitcoin-io" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0e67dbf7a9971e7f4276f6089e9e814ce0f624a03216b7d92d00351ae7fb3e" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee" dependencies = [ "bitcoincore-rpc-json", "jsonrpc", @@ -312,9 +349,9 @@ dependencies = [ [[package]] name = "bitcoincore-rpc-json" -version = "0.15.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2ae16202721ba8c3409045681fac790a5ddc791f05731a2df22c0c6bffc0f1" +checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a" dependencies = [ "bitcoin", "serde", @@ -327,6 +364,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + [[package]] name = "block-buffer" version = "0.9.0" @@ -339,9 +382,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -354,24 +397,25 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bstr" -version = "0.2.17" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -381,17 +425,17 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.1.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -433,9 +477,9 @@ dependencies = [ [[package]] name = "chunked_transfer" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" [[package]] name = "cipher" @@ -454,7 +498,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -463,31 +507,31 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49be99e6e5ad55d420884b5b2a68aca890bcd1a1540ed6d2892363623a60f538" +checksum = "55eefc811f7d5280586dec7342824a84ab81f1d7e0cdb4cd579c1470e3e236cc" dependencies = [ "anyhow", - "bytes 1.1.0", - "env_logger", + "bytes 1.9.0", "futures", "log", "serde", "serde_json", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.13", + "tracing", + "tracing-subscriber", ] [[package]] name = "colored" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "atty", "lazy_static", - "winapi 0.3.9", + "windows-sys 0.59.0", ] [[package]] @@ -498,9 +542,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -508,9 +552,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -523,18 +567,18 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -565,28 +609,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "deadpool" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" -dependencies = [ - "async-trait", - "deadpool-runtime", - "num_cpus", - "retain_mut", - "tokio 1.36.0", -] - -[[package]] -name = "deadpool-runtime" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "der-parser" @@ -613,15 +638,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.98", + "syn 2.0.96", ] [[package]] @@ -639,26 +664,32 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.4", "crypto-common", ] [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] -name = "ed25519" -version = "1.5.2" +name = "dnssec-prover" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "96487aad690d45a83f2b9876828ba856c5430bbb143cb5730d8a5d04a4805179" + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -679,32 +710,19 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -713,23 +731,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "errno-dragonfly", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.59.0", ] [[package]] @@ -746,18 +753,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.7.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" @@ -782,11 +786,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] @@ -802,7 +805,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -814,9 +817,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -829,9 +832,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -839,15 +842,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -856,38 +859,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -896,16 +899,16 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -924,9 +927,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "libc", @@ -934,22 +937,34 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.28.1" +name = "getrandom" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ - "aho-corasick 0.7.18", + "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -963,8 +978,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.8.0", + "http 0.2.12", + "indexmap 1.9.3", "slab", "tokio 0.2.25", "tokio-util 0.3.1", @@ -974,20 +989,20 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.7.1", "slab", - "tokio 1.36.0", - "tokio-util 0.7.10", + "tokio 1.43.0", + "tokio-util 0.7.13", "tracing", ] @@ -1002,9 +1017,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashlink" @@ -1017,18 +1044,17 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.0", - "bitflags", - "bytes 1.1.0", + "base64 0.21.7", + "bytes 1.9.0", "headers-core", - "http", - "httpdate 1.0.2", + "http 0.2.12", + "httpdate 1.0.3", "mime", - "sha-1", + "sha1", ] [[package]] @@ -1037,7 +1063,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.12", ] [[package]] @@ -1051,9 +1077,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1066,12 +1092,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1082,6 +1105,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hmac" version = "0.11.0" @@ -1094,11 +1132,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.3" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "winapi 0.3.9", + "windows-sys 0.59.0", ] [[package]] @@ -1107,9 +1145,20 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "fnv", - "itoa 1.0.10", + "itoa 1.0.14", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes 1.9.0", + "fnv", + "itoa 1.0.14", ] [[package]] @@ -1119,7 +1168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.6", - "http", + "http 0.2.12", ] [[package]] @@ -1128,16 +1177,16 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.1.0", - "http", - "pin-project-lite 0.2.13", + "bytes 1.9.0", + "http 0.2.12", + "pin-project-lite 0.2.16", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1147,15 +1196,9 @@ checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -1168,7 +1211,7 @@ dependencies = [ "futures-core", "futures-util", "h2 0.2.7", - "http", + "http 0.2.12", "http-body 0.3.1", "httparse", "httpdate 0.3.2", @@ -1183,23 +1226,23 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "futures-channel", "futures-core", "futures-util", - "h2 0.3.25", - "http", + "h2 0.3.26", + "http 0.2.12", "http-body 0.4.6", "httparse", - "httpdate 1.0.2", - "itoa 1.0.10", - "pin-project-lite 0.2.13", - "socket2 0.5.6", - "tokio 1.36.0", + "httpdate 1.0.3", + "itoa 1.0.14", + "pin-project-lite 0.2.16", + "socket2 0.5.8", + "tokio 1.43.0", "tower-service", "tracing", "want", @@ -1211,9 +1254,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.28", - "pin-project-lite 0.2.13", - "tokio 1.36.0", + "hyper 0.14.32", + "pin-project-lite 0.2.16", + "tokio 1.43.0", "tokio-io-timeout", ] @@ -1223,63 +1266,181 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.28", + "bytes 1.9.0", + "hyper 0.14.32", "native-tls", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-native-tls", ] [[package]] -name = "idna" -version = "0.2.3" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] name = "indexmap" -version = "2.2.5" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.15.2", ] [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "io-lifetimes" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" -dependencies = [ - "libc", - "windows-sys 0.42.0", -] - [[package]] name = "iovec" version = "0.1.4" @@ -1291,27 +1452,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "is-terminal" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" -dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes", - "rustix", - "windows-sys 0.42.0", -] +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" -version = "0.10.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -1324,28 +1473,29 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "jsonrpc" -version = "0.12.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8423b78fc94d12ef1a4a9d13c348c9a78766dda0cc18817adf0faf77e670c8" +checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" dependencies = [ - "base64-compat", + "base64 0.13.1", + "minreq", "serde", - "serde_derive", "serde_json", ] @@ -1399,9 +1549,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures 0.2.17", +] [[package]] name = "kernel32-sys" @@ -1415,15 +1568,21 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" @@ -1438,64 +1597,99 @@ dependencies = [ [[package]] name = "lightning" -version = "0.0.108" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d885bf509066af86ae85354c8959028ad6192c22a2657ef8271e94029d30f9d0" +checksum = "b3224b577def19c2bb3dcf2c35a95d94909183204c061746d1245ecc6e889e8e" dependencies = [ + "bech32", "bitcoin", + "dnssec-prover", + "hashbrown 0.13.2", + "libm", + "lightning-invoice", + "lightning-types", + "possiblyrandom", ] [[package]] name = "lightning-block-sync" -version = "0.0.108" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f1ed50f41785af19f5cd1225b668e87ef0d59bb84e6f8ef2542933e6082a2c" +checksum = "baab5bdee174a2047d939a4ca0dc2e1c23caa0f8cab0b4380aed77a20e116f1e" dependencies = [ "bitcoin", "chunked_transfer", - "futures", "lightning", - "serde", "serde_json", ] [[package]] -name = "lightning-net-tokio" -version = "0.0.108" +name = "lightning-invoice" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" +checksum = "d4254e7d05961a3728bc90737c522e7091735ba6f2f71014096d4b3eb4ee5d89" +dependencies = [ + "bech32", + "bitcoin", + "lightning-types", +] + +[[package]] +name = "lightning-net-tokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6a6c93b1e592f1d46bb24233cac4a33b4015c99488ee229927a81d16226e45" dependencies = [ "bitcoin", "lightning", - "tokio 1.36.0", + "tokio 1.43.0", +] + +[[package]] +name = "lightning-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cd84d4e71472035903e43caded8ecc123066ce466329ccd5ae537a8d5488c7" +dependencies = [ + "bitcoin", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] -name = "matches" -version = "0.1.9" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] [[package]] name = "matchit" @@ -1505,21 +1699,21 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1533,11 +1727,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ - "adler", + "adler2", +] + +[[package]] +name = "minreq" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0c420feb01b9fb5061f8c8f452534361dd783756dcf38ec45191ce55e7a161" +dependencies = [ + "log", + "serde", + "serde_json", ] [[package]] @@ -1561,13 +1766,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1584,16 +1789,14 @@ dependencies = [ [[package]] name = "mockito" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa08cccbc31b07113e4322d79df4464e0ed888032fc67ec56136325cd3afecf" +checksum = "406f43768da5a859ce19bb0978fd8dc2167a7d9a52f3935c6a187242e1a4ff9f" dependencies = [ "assert-json-diff", - "async-trait", "colored", - "deadpool", "futures", - "hyper 0.14.28", + "hyper 0.14.32", "lazy_static", "log", "rand 0.8.5", @@ -1601,7 +1804,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "similar", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -1610,10 +1813,10 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "encoding_rs", "futures-util", - "http", + "http 0.2.12", "httparse", "log", "memchr", @@ -1624,17 +1827,16 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1648,9 +1850,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.37" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1659,21 +1861,30 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "num-bigint" -version = "0.4.3" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi 0.3.9", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1686,79 +1897,78 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.40" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ - "bitflags", + "bitflags 2.8.0", "cfg-if 1.0.0", "foreign-types", "libc", @@ -1769,34 +1979,39 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.11.2" @@ -1805,90 +2020,90 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.10", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.5.8", "smallvec", - "windows-sys 0.42.0", + "windows-targets 0.52.6", ] [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.2", + "base64 0.22.1", "serde", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 1.8.0", + "indexmap 2.7.1", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -1899,9 +2114,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1911,9 +2126,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "poly1305" @@ -1921,11 +2136,20 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.1", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] +[[package]] +name = "possiblyrandom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b122a615d72104fb3d8b26523fdf9232cd8ee06949fb37e4ce3ff964d15dffd" +dependencies = [ + "getrandom 0.2.15", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1934,18 +2158,21 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -1957,7 +2184,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.98", + "syn 1.0.109", "version_check", ] @@ -1974,31 +2201,31 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "prost-derive", ] [[package]] name = "prost-build" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ - "bytes 1.1.0", - "heck 0.4.1", + "bytes 1.9.0", + "heck 0.5.0", "itertools", "log", "multimap", @@ -2008,38 +2235,37 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.43", + "syn 2.0.96", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] name = "prost-types" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2078,7 +2304,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2098,7 +2324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2127,11 +2353,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.15", ] [[package]] @@ -2145,9 +2371,9 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ "pem", "ring", @@ -2168,41 +2394,65 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "aho-corasick 1.1.3", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "remove_dir_all" @@ -2215,32 +2465,35 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.13.0", - "bytes 1.1.0", + "base64 0.21.7", + "bytes 1.9.0", "encoding_rs", "futures-core", "futures-util", - "h2 0.3.25", - "http", + "h2 0.3.26", + "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.32", "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "tokio 1.36.0", + "sync_wrapper", + "system-configuration", + "tokio 1.43.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2251,24 +2504,19 @@ dependencies = [ "winreg", ] -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if 1.0.0", + "getrandom 0.2.15", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2277,7 +2525,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2288,15 +2536,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -2312,23 +2560,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.8.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", @@ -2340,34 +2587,33 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.2", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2376,64 +2622,65 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.59.0", ] [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] [[package]] name = "security-framework" -version = "2.6.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -2442,9 +2689,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -2452,38 +2699,39 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.9" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ - "indexmap 1.8.0", - "itoa 1.0.10", + "indexmap 2.7.1", + "itoa 1.0.14", + "memchr", "ryu", "serde", ] @@ -2495,30 +2743,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.10", + "itoa 1.0.14", "ryu", "serde", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures 0.2.1", - "digest 0.10.7", -] - [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -2530,7 +2767,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -2548,31 +2785,46 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.5.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "similar" -version = "2.2.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_logger" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea" +checksum = "48047e77b528151aaf841a10a9025f9459da80ba820e425ff7eb005708a76dc7" dependencies = [ "atty", "colored", @@ -2583,15 +2835,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -2606,9 +2861,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2620,6 +2875,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.8.0" @@ -2647,20 +2908,20 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.98", + "syn 1.0.109", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2669,9 +2930,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2684,18 +2945,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.98", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.1" @@ -2704,7 +2953,28 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -2719,22 +2989,23 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "teos" version = "0.2.0" dependencies = [ + "base64 0.22.1", "bitcoin", "bitcoincore-rpc", "hex", @@ -2754,7 +3025,7 @@ dependencies = [ "structopt", "tempdir", "teos-common", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-stream", "toml", "tonic", @@ -2781,15 +3052,6 @@ dependencies = [ "tonic-build", ] -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - [[package]] name = "textwrap" version = "0.11.0" @@ -2801,32 +3063,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", - "itoa 1.0.10", + "itoa 1.0.14", "libc", "num-conv", "num_threads", @@ -2844,29 +3116,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.5.1" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "tokio" version = "0.2.25" @@ -2887,21 +3154,20 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", - "bytes 1.1.0", + "bytes 1.9.0", "libc", - "mio 0.8.11", - "num_cpus", - "parking_lot 0.12.1", - "pin-project-lite 0.2.13", + "mio 1.0.3", + "parking_lot 0.12.3", + "pin-project-lite 0.2.16", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.8", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2910,29 +3176,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.13", - "tokio 1.36.0", + "pin-project-lite 0.2.16", + "tokio 1.43.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -2943,41 +3209,41 @@ checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", "rustls-pki-types", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] name = "tokio-socks" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", - "pin-project-lite 0.2.13", - "tokio 1.36.0", + "pin-project-lite 0.2.16", + "tokio 1.43.0", ] [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "tokio 1.36.0", + "tokio 1.43.0", "tungstenite", ] @@ -2997,23 +3263,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "futures-core", "futures-sink", - "pin-project-lite 0.2.13", - "tokio 1.36.0", - "tracing", + "pin-project-lite 0.2.16", + "tokio 1.43.0", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -3027,19 +3292,19 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.2", - "bytes 1.1.0", - "h2 0.3.25", - "http", + "base64 0.21.7", + "bytes 1.9.0", + "h2 0.3.26", + "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.32", "hyper-timeout", "percent-encoding", "pin-project", "prost", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.2.0", "rustls-pki-types", - "tokio 1.36.0", + "tokio 1.43.0", "tokio-rustls", "tokio-stream", "tower", @@ -3058,7 +3323,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] @@ -3068,7 +3333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69" dependencies = [ "base32", - "base64 0.13.0", + "base64 0.13.1", "derive_more", "ed25519-dalek", "hex", @@ -3078,7 +3343,7 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "tokio 1.36.0", + "tokio 1.43.0", ] [[package]] @@ -3089,13 +3354,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap 1.8.0", + "indexmap 1.9.3", "pin-project", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", "rand 0.8.5", "slab", - "tokio 1.36.0", - "tokio-util 0.7.10", + "tokio 1.43.0", + "tokio-util 0.7.13", "tower-layer", "tower-service", "tracing", @@ -3103,46 +3368,47 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", - "pin-project-lite 0.2.13", + "pin-project-lite 0.2.16", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.96", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -3155,6 +3421,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "triggered" version = "0.1.2" @@ -3163,20 +3458,20 @@ checksum = "ce148eae0d1a376c1b94ae651fc3261d9cb8294788b962b7382066376503a2d1" [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ - "base64 0.13.0", "byteorder", - "bytes 1.1.0", - "http", + "bytes 1.9.0", + "data-encoding", + "http 1.2.0", "httparse", "log", "rand 0.8.5", @@ -3188,57 +3483,33 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "universal-hash" @@ -3258,13 +3529,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.2.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3274,6 +3544,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3288,47 +3576,44 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] [[package]] name = "warp" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" dependencies = [ - "bytes 1.1.0", + "bytes 1.9.0", "futures-channel", "futures-util", "headers", - "http", - "hyper 0.14.28", + "http 0.2.12", + "hyper 0.14.32", "log", "mime", "mime_guess", "multer", "percent-encoding", "pin-project", - "rustls-pemfile 1.0.3", "scoped-tls", "serde", "serde_json", "serde_urlencoded", - "tokio 1.36.0", - "tokio-stream", + "tokio 1.43.0", "tokio-tungstenite", - "tokio-util 0.7.10", + "tokio-util 0.7.13", "tower-service", "tracing", ] @@ -3346,47 +3631,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.81" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if 1.0.0", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", - "lazy_static", "log", "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if 1.0.0", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3394,22 +3690,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "watchtower-plugin" @@ -3428,31 +3727,20 @@ dependencies = [ "serde_json", "tempdir", "teos-common", - "tokio 1.36.0", + "tokio 1.43.0", "tonic", ] [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" -dependencies = [ - "either", - "lazy_static", - "libc", -] - [[package]] name = "winapi" version = "0.2.8" @@ -3481,49 +3769,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.0", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm 0.42.0", - "windows_x86_64_msvc 0.42.0", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3539,7 +3790,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3559,25 +3819,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3586,21 +3841,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3610,21 +3853,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3634,21 +3865,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.36.1" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3658,21 +3883,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3682,15 +3895,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3700,21 +3907,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3724,19 +3919,41 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi 0.3.9", + "cfg-if 1.0.0", + "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -3775,22 +3992,109 @@ dependencies = [ ] [[package]] -name = "zeroize" -version = "1.7.0" +name = "yoke" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", - "synstructure 0.12.6", + "syn 2.0.96", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index 76af6ed..a070916 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ members = [ "teos", "teos-common", "watchtower-plugin" -] \ No newline at end of file +] diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 4efa204..3ea5e11 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -6,7 +6,7 @@ - `bitcoind` ### Minimum Supported Rust Version (MSRV) -FIXME: Define MSRV +Refer to [toolchain](./rust-toolchain.toml) ### Installing Rust Refer to [rust-lang.org](https://www.rust-lang.org/tools/install). diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8a95a07 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "1.81.0" +components = [ + "rustfmt", + "clippy", +] diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index 1776689..14a45d4 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -20,8 +20,8 @@ rand = "0.8.4" chacha20poly1305 = "0.8.0" # Bitcoin and Lightning -bitcoin = { version = "0.28.0", features = [ "use-serde" ] } -lightning = "0.0.108" +bitcoin = { version = "0.32.0", features = [ "serde" ] } +lightning = "0.1.0" [build-dependencies] -tonic-build = "0.11" \ No newline at end of file +tonic-build = "0.11" diff --git a/teos-common/src/cryptography.rs b/teos-common/src/cryptography.rs index cf5fef3..5dee477 100644 --- a/teos-common/src/cryptography.rs +++ b/teos-common/src/cryptography.rs @@ -1,10 +1,9 @@ //! Cryptography module, used in the interaction between users and towers. -use rand::distributions::Uniform; -use rand::Rng; - use chacha20poly1305::aead::{Aead, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; +use rand::distributions::Uniform; +use rand::Rng; use bitcoin::consensus; use bitcoin::hashes::{sha256, Hash}; @@ -20,7 +19,7 @@ pub enum DecryptingError { } /// Shadows [message_signing::sign]. -pub fn sign(msg: &[u8], sk: &SecretKey) -> Result { +pub fn sign(msg: &[u8], sk: &SecretKey) -> String { message_signing::sign(msg, sk) } @@ -47,8 +46,8 @@ pub fn encrypt( ) -> Result, chacha20poly1305::aead::Error> { // Defaults is [0; 12] let nonce = Nonce::default(); - let _k = sha256::Hash::hash(secret); - let key = Key::from_slice(&_k); + let k = sha256::Hash::hash(secret.as_byte_array()); + let key = Key::from_slice(k.as_byte_array()); let cypher = ChaCha20Poly1305::new(key); cypher.encrypt(&nonce, consensus::serialize(message).as_ref()) @@ -64,8 +63,8 @@ pub fn encrypt( pub fn decrypt(encrypted_blob: &[u8], secret: &Txid) -> Result { // Defaults is [0; 12] let nonce = Nonce::default(); - let _k = sha256::Hash::hash(secret); - let key = Key::from_slice(&_k); + let k = sha256::Hash::hash(secret.as_byte_array()); + let key = Key::from_slice(k.as_byte_array()); let cypher = ChaCha20Poly1305::new(key); @@ -95,6 +94,8 @@ pub fn get_random_keypair() -> (SecretKey, PublicKey) { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use bitcoin::consensus; use bitcoin::hashes::hex::FromHex; @@ -108,8 +109,8 @@ mod tests { let expected_enc_blob = Vec::from_hex(ENC_BLOB).unwrap(); let tx_bytes = Vec::from_hex(HEX_TX).unwrap(); - let tx = consensus::deserialize(&tx_bytes).unwrap(); - let txid = Txid::from_hex(HEX_TXID).unwrap(); + let tx: Transaction = consensus::deserialize(&tx_bytes).unwrap(); + let txid = bitcoin::Txid::from_str(HEX_TXID).unwrap(); assert_eq!(encrypt(&tx, &txid).unwrap(), expected_enc_blob); } @@ -118,7 +119,7 @@ mod tests { let expected_tx = consensus::deserialize(&Vec::from_hex(HEX_TX).unwrap()).unwrap(); let encrypted_blob = Vec::from_hex(ENC_BLOB).unwrap(); - let txid = Txid::from_hex(HEX_TXID).unwrap(); + let txid = bitcoin::Txid::from_str(HEX_TXID).unwrap(); assert_eq!(decrypt(&encrypted_blob, &txid).unwrap(), expected_tx); } } diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 699c3c5..5dc0e00 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -92,8 +92,7 @@ impl RegistrationReceipt { } pub fn sign(&mut self, sk: &SecretKey) { - // TODO: Check if there's any case where this can actually fail. Don't unwrap if so. - self.signature = Some(cryptography::sign(&self.to_vec(), sk).unwrap()); + self.signature = Some(cryptography::sign(&self.to_vec(), sk)); } pub fn verify(&self, id: &UserId) -> bool { @@ -153,8 +152,7 @@ impl AppointmentReceipt { } pub fn sign(&mut self, sk: &SecretKey) { - // TODO: Check if there's any case where this can actually fail. Don't unwrap if so. - self.signature = Some(cryptography::sign(&self.to_vec(), sk).unwrap()); + self.signature = Some(cryptography::sign(&self.to_vec(), sk)); } pub fn verify(&self, id: &UserId) -> bool { diff --git a/teos-common/src/test_utils.rs b/teos-common/src/test_utils.rs index 0b5a95b..4bd5cb9 100644 --- a/teos-common/src/test_utils.rs +++ b/teos-common/src/test_utils.rs @@ -1,5 +1,6 @@ use std::convert::TryInto; +use bitcoin::script::PushBytesBuf; use hex::FromHex; use rand::distributions::Standard; use rand::prelude::Distribution; @@ -7,7 +8,7 @@ use rand::Rng; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; -use bitcoin::{consensus, Script, Transaction, TxOut, Txid}; +use bitcoin::{consensus, Amount, ScriptBuf, Transaction, TxOut, Txid}; use crate::appointment::{Appointment, Locator}; use crate::cryptography; @@ -48,13 +49,16 @@ pub fn generate_random_appointment(dispute_txid: Option<&Txid>) -> Appointment { let tx_bytes = Vec::from_hex(TX_HEX).unwrap(); let mut penalty_tx: Transaction = consensus::deserialize(&tx_bytes).unwrap(); + let size = get_random_int::() % 81; + let mut push_bytes_buf = PushBytesBuf::new(); + PushBytesBuf::extend_from_slice(&mut push_bytes_buf, &cryptography::get_random_bytes(size)) + .unwrap(); + let script_pubkey = ScriptBuf::new_op_return(push_bytes_buf); // Append a random-sized OP_RETURN to make each transcation random in size. penalty_tx.output.push(TxOut { - value: 0, - script_pubkey: Script::new_op_return(&cryptography::get_random_bytes( - get_random_int::() % 81, - )), + value: Amount::from_sat(0), + script_pubkey, }); let mut raw_locator: [u8; 16] = cryptography::get_random_bytes(16).try_into().unwrap(); diff --git a/teos/Cargo.toml b/teos/Cargo.toml index 0fe9339..287b0aa 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -32,13 +32,14 @@ tokio = { version = "1.5", features = [ "rt-multi-thread" ] } triggered = "0.1.2" warp = "0.3.5" torut = "0.2.1" +base64 = "0.22.1" # Bitcoin and Lightning -bitcoin = { version = "0.28.0", features = [ "base64" ] } -bitcoincore-rpc = "0.15.0" -lightning = "0.0.108" -lightning-net-tokio = "0.0.108" -lightning-block-sync = { version = "0.0.108", features = [ "rpc-client" ] } +bitcoin = { version = "0.32.0" } +bitcoincore-rpc = "0.19.0" +lightning = "0.1.0" +lightning-net-tokio = "0.1.0" +lightning-block-sync = { version = "0.1.0", features = [ "rpc-client" ] } # Local teos-common = { path = "../teos-common" } diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index a041ee3..949c08b 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -767,7 +767,7 @@ mod tests_methods { // Then try to add an appointment let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); let response = request_to_api::< common_msgs::AddAppointmentRequest, @@ -793,7 +793,7 @@ mod tests_methods { let (server_addr, _s) = run_tower_in_background().await; let (user_sk, _s) = cryptography::get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); assert_eq!( check_api_error( @@ -845,8 +845,8 @@ mod tests_methods { .add_dummy_tracker_to_responder(&tracker); // Try to add it via the http API - let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); assert_eq!( check_api_error( Endpoint::AddAppointment, @@ -875,7 +875,7 @@ mod tests_methods { .await; let (user_sk, _) = cryptography::get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); assert_eq!( check_api_error( @@ -915,7 +915,7 @@ mod tests_methods { // Add an appointment let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); request_to_api::( Endpoint::AddAppointment, @@ -939,8 +939,7 @@ mod tests_methods { signature: cryptography::sign( format!("get appointment {}", appointment.locator).as_bytes(), &user_sk, - ) - .unwrap(), + ), }, server_addr, ) @@ -970,7 +969,6 @@ mod tests_methods { format!("get appointment {}", appointment.locator).as_bytes(), &user_sk, ) - .unwrap() })), server_addr, ) @@ -1013,7 +1011,6 @@ mod tests_methods { format!("get appointment {}", appointment.locator).as_bytes(), &user_sk, ) - .unwrap() })), server_addr, ) @@ -1048,7 +1045,6 @@ mod tests_methods { format!("get appointment {}", appointment.locator).as_bytes(), &user_sk, ) - .unwrap() })), server_addr, ) @@ -1086,8 +1082,7 @@ mod tests_methods { >( Endpoint::GetSubscriptionInfo, common_msgs::GetSubscriptionInfoRequest { - signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) - .unwrap(), + signature: cryptography::sign("get subscription info".as_bytes(), &user_sk), }, server_addr, ) @@ -1111,7 +1106,6 @@ mod tests_methods { Endpoint::GetSubscriptionInfo, RequestBody::Json(serde_json::json!(common_msgs::GetSubscriptionInfoRequest { signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) - .unwrap(), })), server_addr, ) @@ -1139,7 +1133,6 @@ mod tests_methods { Endpoint::GetSubscriptionInfo, RequestBody::Json(serde_json::json!(common_msgs::GetSubscriptionInfoRequest { signature: cryptography::sign("get subscription info".as_bytes(), &user_sk) - .unwrap(), })), server_addr, ) diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index fc2085d..8a1e1ea 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -466,7 +466,7 @@ mod tests_private_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk); internal_api .watcher .add_appointment(appointment.clone(), user_signature) @@ -509,7 +509,7 @@ mod tests_private_api { async fn test_get_appointments() { let (internal_api, _s) = create_api().await; - let locator = Locator::new(get_random_tx().txid()).to_vec(); + let locator = Locator::new(get_random_tx().compute_txid()).to_vec(); let response = internal_api .get_appointments(Request::new(msgs::GetAppointmentsRequest { locator })) .await @@ -525,7 +525,7 @@ mod tests_private_api { for i in 0..3 { // Create a dispute tx to be used for creating different dummy appointments with the same locator. - let dispute_txid = get_random_tx().txid(); + let dispute_txid = get_random_tx().compute_txid(); // The number of different appointments to create for this dispute tx. let appointments_to_create = 4 * i + 7; @@ -535,7 +535,7 @@ mod tests_private_api { let (user_sk, user_pk) = get_random_keypair(); internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(Some(&dispute_txid)).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); internal_api .watcher .add_appointment(appointment, signature) @@ -593,7 +593,7 @@ mod tests_private_api { .add_dummy_tracker_to_responder(&tracker); } - let locator = Locator::new(dispute_tx.txid()); + let locator = Locator::new(dispute_tx.compute_txid()); // Query for the current locator and assert it retrieves correct trackers. let response = internal_api @@ -648,7 +648,7 @@ mod tests_private_api { // Add data to the Watcher for _ in 0..2 { let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk); internal_api .watcher .add_appointment(appointment.clone(), user_signature) @@ -731,7 +731,7 @@ mod tests_private_api { // Add an appointment and check back let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - let user_signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); + let user_signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk); internal_api .watcher .add_appointment(appointment.inner, user_signature) @@ -901,7 +901,7 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); let response = internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { @@ -926,7 +926,7 @@ mod tests_public_api { let (user_sk, _) = get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { @@ -955,7 +955,7 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { @@ -984,7 +984,7 @@ mod tests_public_api { internal_api.watcher.register(UserId(user_pk)).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { @@ -1021,8 +1021,8 @@ mod tests_public_api { .add_dummy_tracker_to_responder(&tracker); // Try to add it again using the API. - let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { appointment: Some(appointment.into()), @@ -1047,7 +1047,7 @@ mod tests_public_api { let (user_sk, _) = get_random_keypair(); let appointment = generate_dummy_appointment(None).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); match internal_api .add_appointment(Request::new(common_msgs::AddAppointmentRequest { @@ -1074,7 +1074,7 @@ mod tests_public_api { // Add the appointment let appointment = generate_dummy_appointment(None).inner; - let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk); internal_api .watcher .add_appointment(appointment.clone(), user_signature) @@ -1085,7 +1085,7 @@ mod tests_public_api { let response = internal_api .get_appointment(Request::new(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await .unwrap() @@ -1113,7 +1113,7 @@ mod tests_public_api { match internal_api .get_appointment(Request::new(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1140,7 +1140,7 @@ mod tests_public_api { match internal_api .get_appointment(Request::new(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1168,7 +1168,7 @@ mod tests_public_api { match internal_api .get_appointment(Request::new(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1191,7 +1191,7 @@ mod tests_public_api { match internal_api .get_appointment(Request::new(common_msgs::GetAppointmentRequest { locator: appointment.locator.to_vec(), - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1215,7 +1215,7 @@ mod tests_public_api { let message = "get subscription info".to_string(); let response = internal_api .get_subscription_info(Request::new(common_msgs::GetSubscriptionInfoRequest { - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await .unwrap() @@ -1238,7 +1238,7 @@ mod tests_public_api { let message = "get subscription info".to_string(); match internal_api .get_subscription_info(Request::new(common_msgs::GetSubscriptionInfoRequest { - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1262,7 +1262,7 @@ mod tests_public_api { let message = "get subscription info".to_string(); match internal_api .get_subscription_info(Request::new(common_msgs::GetSubscriptionInfoRequest { - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { @@ -1283,7 +1283,7 @@ mod tests_public_api { let message = "get subscription info".to_string(); match internal_api .get_subscription_info(Request::new(common_msgs::GetSubscriptionInfoRequest { - signature: cryptography::sign(message.as_bytes(), &user_sk).unwrap(), + signature: cryptography::sign(message.as_bytes(), &user_sk), })) .await { diff --git a/teos/src/bitcoin_cli.rs b/teos/src/bitcoin_cli.rs index 22b5338..4b73bde 100644 --- a/teos/src/bitcoin_cli.rs +++ b/teos/src/bitcoin_cli.rs @@ -9,20 +9,19 @@ * at your option. */ +use base64::{engine::general_purpose::URL_SAFE as BASE64, Engine}; use std::convert::TryInto; use std::io::{Error, ErrorKind}; use std::sync::Arc; use tokio::sync::Mutex; -use bitcoin::base64; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::hashes::hex::ToHex; -use bitcoin::{Block, Transaction}; -use bitcoincore_rpc::Auth; +use bitcoin::Transaction; +use bitcoincore_rpc::{Auth, RawTx}; use lightning::util::ser::Writeable; use lightning_block_sync::http::{HttpEndpoint, JsonResponse}; use lightning_block_sync::rpc::RpcClient; -use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource}; +use lightning_block_sync::{AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource}; /// A simple implementation of a bitcoind client (`bitcoin-cli`) with the minimal functionality required by the tower. pub struct BitcoindClient<'a> { @@ -52,7 +51,10 @@ impl BlockSource for &BitcoindClient<'_> { } /// Gets a block given its hash. - fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> { + fn get_block<'a>( + &'a self, + header_hash: &'a BlockHash, + ) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { let rpc = self.bitcoind_rpc_client.lock().await; rpc.get_block(header_hash).await @@ -101,8 +103,8 @@ impl<'a> BitcoindClient<'a> { } }?; - let rpc_credentials = base64::encode(&format!("{}:{}", rpc_user, rpc_password)); - let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; + let rpc_credentials = BASE64.encode(format!("{}:{}", rpc_user, rpc_password)); + let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint); let client = Self { bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)), @@ -127,9 +129,10 @@ impl<'a> BitcoindClient<'a> { } /// Gets a fresh RPC client. - pub fn get_new_rpc_client(&self) -> std::io::Result { + pub fn get_new_rpc_client(&self) -> RpcClient { let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port); - let rpc_credentials = base64::encode(&format!("{}:{}", self.rpc_user, self.rpc_password)); + let rpc_credentials = BASE64.encode(format!("{}:{}", self.rpc_user, self.rpc_password)); + RpcClient::new(&rpc_credentials, http_endpoint) } @@ -146,7 +149,7 @@ impl<'a> BitcoindClient<'a> { pub async fn send_raw_transaction(&self, raw_tx: &Transaction) -> Result { let rpc = self.bitcoind_rpc_client.lock().await; - let raw_tx_json = serde_json::json!(raw_tx.encode().to_hex()); + let raw_tx_json = serde_json::json!(raw_tx.encode().raw_hex()); rpc.call_method::("sendrawtransaction", &[raw_tx_json]) .await } @@ -155,7 +158,7 @@ impl<'a> BitcoindClient<'a> { pub async fn get_raw_transaction(&self, txid: &Txid) -> Result { let rpc = self.bitcoind_rpc_client.lock().await; - let txid_hex = serde_json::json!(txid.encode().to_hex()); + let txid_hex = serde_json::json!(txid.encode().raw_hex()); rpc.call_method::("getrawtransaction", &[txid_hex]) .await } diff --git a/teos/src/carrier.rs b/teos/src/carrier.rs index 15c9d83..75d0fe6 100644 --- a/teos/src/carrier.rs +++ b/teos/src/carrier.rs @@ -80,17 +80,17 @@ impl Carrier { pub(crate) fn send_transaction(&mut self, tx: &Transaction) -> ConfirmationStatus { self.hang_until_bitcoind_reachable(); - if let Some(receipt) = self.issued_receipts.get(&tx.txid()) { - log::info!("Transaction already sent: {}", tx.txid()); + if let Some(receipt) = self.issued_receipts.get(&tx.compute_txid()) { + log::info!("Transaction already sent: {}", tx.compute_txid()); return *receipt; } - log::info!("Pushing transaction to the network: {}", tx.txid()); + log::info!("Pushing transaction to the network: {}", tx.compute_txid()); let receipt = match self.bitcoin_cli.send_raw_transaction(tx) { Ok(_) => { // Here the transaction could, potentially, have been in mempool before the current height. // This shouldn't really matter though. - log::info!("Transaction successfully delivered: {}", tx.txid()); + log::info!("Transaction successfully delivered: {}", tx.compute_txid()); ConfirmationStatus::InMempoolSince(self.block_height) } Err(JsonRpcError(RpcError(rpcerr))) => match rpcerr.code { @@ -106,7 +106,7 @@ impl Carrier { rpc_errors::RPC_VERIFY_ALREADY_IN_CHAIN => { log::info!( "Transaction was confirmed long ago, not keeping track of it: {}", - tx.txid() + tx.compute_txid() ); // Given we are not using txindex, if a transaction bounces we cannot get its confirmation count. However, [send_transaction] is guarded by @@ -117,7 +117,7 @@ impl Carrier { rpc_errors::RPC_DESERIALIZATION_ERROR => { // Adding this here just for completeness. We should never end up here. The Carrier only sends txs handed by the Responder, // who receives them from the Watcher, who checks that the tx can be properly deserialized. - log::info!("Transaction cannot be deserialized: {}", tx.txid()); + log::info!("Transaction cannot be deserialized: {}", tx.compute_txid()); ConfirmationStatus::Rejected(rpc_errors::RPC_DESERIALIZATION_ERROR) } _ => { @@ -139,7 +139,7 @@ impl Carrier { } }; - self.issued_receipts.insert(tx.txid(), receipt); + self.issued_receipts.insert(tx.compute_txid(), receipt); receipt } @@ -184,6 +184,7 @@ impl Carrier { #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; use std::thread; use crate::test_utils::{get_random_tx, start_server, BitcoindMock, MockOptions, START_HEIGHT}; @@ -217,7 +218,7 @@ mod tests { // Lets add some dummy data into the cache for i in 0..10 { carrier.issued_receipts.insert( - get_random_tx().txid(), + get_random_tx().compute_txid(), ConfirmationStatus::ConfirmedIn(start_height - i), ); } @@ -243,7 +244,7 @@ mod tests { assert_eq!(r, ConfirmationStatus::InMempoolSince(start_height)); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -261,7 +262,7 @@ mod tests { assert_eq!(r, ConfirmationStatus::InMempoolSince(start_height)); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -284,7 +285,7 @@ mod tests { ); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -306,7 +307,7 @@ mod tests { ); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -326,7 +327,7 @@ mod tests { assert_eq!(r, ConfirmationStatus::IrrevocablyResolved); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -348,7 +349,7 @@ mod tests { ); // Check the receipt is on the cache - assert_eq!(carrier.issued_receipts.get(&tx.txid()).unwrap(), &r); + assert_eq!(carrier.issued_receipts.get(&tx.compute_txid()).unwrap(), &r); } #[test] @@ -389,7 +390,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let txid = Txid::from_hex(TXID_HEX).unwrap(); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(carrier.in_mempool(&txid)); } @@ -402,7 +403,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let txid = Txid::from_hex(TXID_HEX).unwrap(); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -417,7 +418,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let txid = Txid::from_hex(TXID_HEX).unwrap(); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -431,7 +432,7 @@ mod tests { start_server(bitcoind_mock.server); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, start_height); - let txid = Txid::from_hex(TXID_HEX).unwrap(); + let txid = Txid::from_str(TXID_HEX).unwrap(); assert!(!carrier.in_mempool(&txid)); } @@ -444,7 +445,7 @@ mod tests { let start_height = START_HEIGHT as u32; let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable.clone(), start_height); - let txid = Txid::from_hex(TXID_HEX).unwrap(); + let txid = Txid::from_str(TXID_HEX).unwrap(); let delay = std::time::Duration::new(3, 0); thread::spawn(move || { diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 3553401..50a690b 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -26,7 +26,7 @@ where { /// A bitcoin client to poll best tips from. spv_client: SpvClient<'a, P, C, L>, - /// The lat known block header by the [ChainMonitor]. + /// The last known block header by the [ChainMonitor]. last_known_block_header: ValidatedBlockHeader, /// A [DBM] (database manager) instance. Used to persist block data into disk. dbm: Arc>, @@ -135,8 +135,8 @@ mod tests { use std::iter::FromIterator; use std::thread; - use bitcoin::network::constants::Network; use bitcoin::BlockHash; + use bitcoin::Network; use lightning_block_sync::{poll::ChainPoller, SpvClient, UnboundedCache}; use crate::test_utils::{Blockchain, START_HEIGHT}; @@ -158,7 +158,7 @@ mod tests { impl chain::Listen for DummyListener { fn filtered_block_connected( &self, - header: &bitcoin::BlockHeader, + header: &bitcoin::block::Header, _: &chain::transaction::TransactionData, _: u32, ) { @@ -167,7 +167,7 @@ mod tests { .insert(header.block_hash()); } - fn block_disconnected(&self, header: &bitcoin::BlockHeader, _: u32) { + fn block_disconnected(&self, header: &bitcoin::block::Header, _: u32) { self.disconnected_blocks .borrow_mut() .insert(header.block_hash()); diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index a36c9df..7322628 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -689,7 +689,7 @@ impl DBM { // DISCUSS: Should we store the txids to avoid pulling raw txs and deserializing then hashing them. let penalty_txid = consensus::deserialize::(&raw_penalty_tx) .unwrap() - .txid(); + .compute_txid(); summaries.insert( UUID::from_slice(&raw_uuid).unwrap(), PenaltySummary::new( @@ -704,7 +704,7 @@ impl DBM { /// Stores the last known block into the database. pub(crate) fn store_last_known_block(&self, block_hash: &BlockHash) -> Result<(), Error> { let query = "INSERT OR REPLACE INTO last_known_block (id, block_hash) VALUES (0, ?)"; - self.store_data(query, params![block_hash.to_vec()]) + self.store_data(query, params![block_hash.to_byte_array().to_vec()]) } /// Loads the last known block from the database. @@ -1130,7 +1130,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let mut appointments = HashMap::new(); let dispute_tx = get_random_tx(); - let dispute_txid = dispute_tx.txid(); + let dispute_txid = dispute_tx.compute_txid(); let locator = Locator::new(dispute_txid); for i in 1..11 { @@ -1316,7 +1316,7 @@ mod tests { let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); let dispute_tx = get_random_tx(); - let dispute_txid = dispute_tx.txid(); + let dispute_txid = dispute_tx.compute_txid(); let mut uuids = HashSet::new(); // Add ten appointments triggered by the same locator. @@ -1336,7 +1336,7 @@ mod tests { let user_id = get_random_user_id(); dbm.store_user(user_id, &user).unwrap(); - let dispute_txid = get_random_tx().txid(); + let dispute_txid = get_random_tx().compute_txid(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); dbm.store_appointment(uuid, &appointment).unwrap(); @@ -1516,7 +1516,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let mut trackers = HashMap::new(); let dispute_tx = get_random_tx(); - let dispute_txid = dispute_tx.txid(); + let dispute_txid = dispute_tx.compute_txid(); let locator = Locator::new(dispute_txid); let status = ConfirmationStatus::InMempoolSince(42); @@ -1700,8 +1700,10 @@ mod tests { let tracker = get_random_tracker(user_id, status); dbm.store_tracker(uuid, &tracker).unwrap(); - penalties_summaries - .insert(uuid, PenaltySummary::new(tracker.penalty_tx.txid(), status)); + penalties_summaries.insert( + uuid, + PenaltySummary::new(tracker.penalty_tx.compute_txid(), status), + ); } assert_eq!(dbm.load_penalties_summaries(), penalties_summaries); diff --git a/teos/src/extended_appointment.rs b/teos/src/extended_appointment.rs index e7ba8f4..6545a7f 100644 --- a/teos/src/extended_appointment.rs +++ b/teos/src/extended_appointment.rs @@ -23,7 +23,7 @@ impl UUID { pub fn new(locator: Locator, user_id: UserId) -> Self { let mut uuid_data = locator.to_vec(); uuid_data.extend(user_id.0.serialize()); - UUID(ripemd160::Hash::hash(&uuid_data).into_inner()) + UUID(ripemd160::Hash::hash(&uuid_data).to_byte_array()) } /// Serializes the [UUID] returning its byte representation. diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index e5d490e..99e7452 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -297,7 +297,7 @@ impl chain::Listen for Gatekeeper { /// This is mainly used to keep track of time and expire / outdate subscriptions when needed. fn filtered_block_connected( &self, - header: &bitcoin::BlockHeader, + header: &bitcoin::block::Header, _: &chain::transaction::TransactionData, height: u32, ) { @@ -324,7 +324,7 @@ impl chain::Listen for Gatekeeper { } /// Handles reorgs in the [Gatekeeper]. Simply updates the last_known_block_height. - fn block_disconnected(&self, header: &bitcoin::BlockHeader, height: u32) { + fn block_disconnected(&self, header: &bitcoin::block::Header, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); // There's nothing to be done here but updating the last known block self.last_known_block_height @@ -435,7 +435,7 @@ mod tests { // Let's now provide data generated by an actual user, still the user is unknown let (user_sk, user_pk) = get_random_keypair(); - let signature = cryptography::sign(message, &user_sk).unwrap(); + let signature = cryptography::sign(message, &user_sk); assert_eq!( gatekeeper.authenticate_user(message, &signature), Err(AuthenticationFailure("User not found.")) diff --git a/teos/src/main.rs b/teos/src/main.rs index 8cc53c0..15c1591 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -3,13 +3,12 @@ use simple_logger::SimpleLogger; use std::fs; use std::io::ErrorKind; use std::ops::{Deref, DerefMut}; -use std::str::FromStr; use std::sync::{Arc, Condvar, Mutex}; use structopt::StructOpt; use tokio::task; use tonic::transport::{Certificate, Server, ServerTlsConfig}; -use bitcoin::network::constants::Network; +use bitcoin::network::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoincore_rpc::{Auth, Client, RpcApi}; use lightning_block_sync::init::validate_best_block_header; @@ -259,13 +258,6 @@ async fn main() { tip.height ); - // This is how chain poller names bitcoin networks. - let btc_network = match conf.btc_network.as_str() { - "main" => "bitcoin", - "test" => "testnet", - any => any, - }; - // Build components let gatekeeper = Arc::new(Gatekeeper::new( tip.height, @@ -275,7 +267,10 @@ async fn main() { dbm.clone(), )); - let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); + let mut poller = ChainPoller::new( + &mut derefed, + Network::from_core_arg(&conf.btc_network).unwrap(), + ); let (responder, watcher) = { let last_n_blocks = get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize) .await.unwrap_or_else(|e| { diff --git a/teos/src/responder.rs b/teos/src/responder.rs index b7412cc..f816aac 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -3,8 +3,10 @@ use std::collections::HashSet; use std::sync::{Arc, Mutex}; +use bitcoin::hashes::Hash; use bitcoin::{consensus, BlockHash}; -use bitcoin::{BlockHeader, Transaction, Txid}; +use bitcoin::{Transaction, Txid}; + use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -93,8 +95,18 @@ impl TransactionTracker { impl From for common_msgs::Tracker { fn from(t: TransactionTracker) -> Self { common_msgs::Tracker { - dispute_txid: t.dispute_tx.txid().to_vec(), - penalty_txid: t.penalty_tx.txid().to_vec(), + dispute_txid: t + .dispute_tx + .compute_txid() + .to_raw_hash() + .to_byte_array() + .to_vec(), + penalty_txid: t + .penalty_tx + .compute_txid() + .to_raw_hash() + .to_byte_array() + .to_vec(), penalty_rawtx: consensus::serialize(&t.penalty_tx), } } @@ -182,9 +194,9 @@ impl Responder { let tx_index = self.tx_index.lock().unwrap(); // Check whether the transaction is in mempool or part of our internal txindex. Send it to our node otherwise. - let status = if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { + let status = if let Some(block_hash) = tx_index.get(&breach.penalty_tx.compute_txid()) { ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) - } else if carrier.in_mempool(&breach.penalty_tx.txid()) { + } else if carrier.in_mempool(&breach.penalty_tx.compute_txid()) { // If it's in mempool we assume it was just included ConfirmationStatus::InMempoolSince(carrier.block_height()) } else { @@ -292,7 +304,7 @@ impl Responder { // Republish all the dispute transactions of the reorged trackers. for uuid in reorged_trackers { let tracker = dbm.load_tracker(uuid).unwrap(); - let dispute_txid = tracker.dispute_tx.txid(); + let dispute_txid = tracker.dispute_tx.compute_txid(); // Try to publish the dispute transaction. let should_publish_penalty = match carrier.send_transaction(&tracker.dispute_tx) { ConfirmationStatus::InMempoolSince(_) => { @@ -368,7 +380,7 @@ impl Responder { let tracker = dbm.load_tracker(uuid).unwrap(); log::warn!( "Penalty transaction has missed many confirmations: {}", - tracker.penalty_tx.txid() + tracker.penalty_tx.compute_txid() ); // Rebroadcast the penalty transaction. let status = carrier.send_transaction(&tracker.penalty_tx); @@ -401,7 +413,7 @@ impl chain::Listen for Responder { /// rebroadcasting is performed for those that have missed too many. fn filtered_block_connected( &self, - header: &BlockHeader, + header: &bitcoin::block::Header, txdata: &chain::transaction::TransactionData, height: u32, ) { @@ -410,7 +422,7 @@ impl chain::Listen for Responder { let txs = txdata .iter() - .map(|(_, tx)| (tx.txid(), header.block_hash())) + .map(|(_, tx)| (tx.compute_txid(), header.block_hash())) .collect(); self.tx_index.lock().unwrap().update(*header, &txs); @@ -444,7 +456,7 @@ impl chain::Listen for Responder { } /// Handles reorgs in the [Responder]. - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &bitcoin::block::Header, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); // Update the carrier and our tx_index. self.carrier.lock().unwrap().update_height(height); @@ -490,7 +502,7 @@ mod tests { impl TransactionTracker { pub fn locator(&self) -> Locator { - Locator::new(self.dispute_tx.txid()) + Locator::new(self.dispute_tx.compute_txid()) } pub fn uuid(&self) -> UUID { @@ -529,7 +541,7 @@ mod tests { pub(crate) fn add_dummy_tracker(&self, tracker: &TransactionTracker) { let (_, appointment) = generate_dummy_appointment_with_user( tracker.user_id, - Some(&tracker.dispute_tx.txid()), + Some(&tracker.dispute_tx.compute_txid()), ); store_appointment_and_its_user(&self.dbm.lock().unwrap(), &appointment); self.dbm @@ -712,7 +724,7 @@ mod tests { let (user_id, uuid) = responder.store_dummy_appointment_to_db(); let breach = get_random_breach(); - let penalty_txid = breach.penalty_tx.txid(); + let penalty_txid = breach.penalty_tx.compute_txid(); // Add the tx to our txindex let target_block_hash = *responder.tx_index.lock().unwrap().blocks().get(2).unwrap(); @@ -913,7 +925,7 @@ mod tests { ConfirmationStatus::InMempoolSince(i), ); just_confirmed.insert(uuid); - txids.insert(breach.penalty_tx.txid()); + txids.insert(breach.penalty_tx.compute_txid()); } 2 => { responder.add_tracker( @@ -1186,7 +1198,7 @@ mod tests { let user_id = users[i % 2]; let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); responder .gatekeeper @@ -1214,7 +1226,7 @@ mod tests { for _ in 0..3 { let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); responder .gatekeeper .add_update_appointment(user_id, uuid, &appointment) @@ -1249,8 +1261,10 @@ mod tests { let mut just_confirmed_trackers = Vec::new(); for i in 0..10 { let dispute_tx = get_random_tx(); - let (uuid, appointment) = - generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.txid())); + let (uuid, appointment) = generate_dummy_appointment_with_user( + standalone_user_id, + Some(&dispute_tx.compute_txid()), + ); responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) @@ -1285,8 +1299,10 @@ mod tests { let mut trackers_to_rebroadcast = Vec::new(); for _ in 0..5 { let dispute_tx = get_random_tx(); - let (uuid, appointment) = - generate_dummy_appointment_with_user(standalone_user_id, Some(&dispute_tx.txid())); + let (uuid, appointment) = generate_dummy_appointment_with_user( + standalone_user_id, + Some(&dispute_tx.compute_txid()), + ); responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) @@ -1317,7 +1333,10 @@ mod tests { .lock() .unwrap() .get_issued_receipts() - .insert(get_random_tx().txid(), ConfirmationStatus::ConfirmedIn(21)); + .insert( + get_random_tx().compute_txid(), + ConfirmationStatus::ConfirmedIn(21), + ); // Connecting a block should trigger all the state transitions let block = chain.generate(Some( @@ -1433,7 +1452,7 @@ mod tests { // Generate appointment and also add it to the DB let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); responder .dbm .lock() diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 4dba0a9..16f9675 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -8,6 +8,7 @@ */ use rand::Rng; +use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex}; use std::thread; @@ -17,22 +18,24 @@ use jsonrpc_http_server::{CloseHandle, Server, ServerBuilder}; use bitcoincore_rpc::{Auth, Client as BitcoindClient}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::block::Block; use bitcoin::blockdata::constants::genesis_block; -use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::script::{Builder, ScriptBuf}; use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxIn, TxOut}; use bitcoin::hash_types::BlockHash; use bitcoin::hash_types::Txid; use bitcoin::hashes::Hash; -use bitcoin::network::constants::Network; -use bitcoin::util::hash::bitcoin_merkle_root; -use bitcoin::util::uint::Uint256; +use bitcoin::merkle_tree::calculate_root; +use bitcoin::pow::Work; +use bitcoin::Amount; +use bitcoin::Network; use bitcoin::Witness; use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, }; use lightning_block_sync::{ - AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, UnboundedCache, + AsyncBlockSourceResult, BlockData, BlockHeaderData, BlockSource, BlockSourceError, + UnboundedCache, }; use teos_common::constants::IRREVOCABLY_RESOLVED; @@ -153,8 +156,11 @@ impl Blockchain { fn at_height_unvalidated(&self, height: usize) -> BlockHeaderData { assert!(!self.blocks.is_empty()); assert!(height < self.blocks.len()); + let height_bytes = height.to_be_bytes(); + let mut padded_bytes = [0u8; 32]; + padded_bytes[32 - height_bytes.len()..].copy_from_slice(&height_bytes); BlockHeaderData { - chainwork: self.blocks[0].header.work() + Uint256::from_u64(height as u64).unwrap(), + chainwork: self.blocks[0].header.work() + Work::from_be_bytes(padded_bytes), height: height as u32, header: self.blocks[height].header, } @@ -184,7 +190,7 @@ impl Blockchain { } pub fn generate(&mut self, txs: Option>) -> Block { - let bits = BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0xff; 32])); + let bits = bitcoin::Target::from_be_bytes([0xff; 32]).to_compact_lossy(); let prev_block = self.blocks.last().unwrap(); let prev_blockhash = prev_block.block_hash(); @@ -199,17 +205,17 @@ impl Blockchain { } None => vec![get_random_tx()], }; - let hashes = txdata.iter().map(|obj| obj.txid().as_hash()); - let mut header = BlockHeader { - version: 0, + let hashes = txdata.iter().map(|tx| tx.compute_txid().to_raw_hash()); + let mut header = bitcoin::block::Header { + version: bitcoin::block::Version::from_consensus(0), prev_blockhash, - merkle_root: bitcoin_merkle_root(hashes).unwrap().into(), + merkle_root: calculate_root(hashes).unwrap().into(), time, bits, nonce: 0, }; - while header.validate_pow(&header.target()).is_err() { + while header.validate_pow(header.target()).is_err() { header.nonce += 1; } @@ -245,7 +251,7 @@ impl BlockSource for Blockchain { }) } - fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> { + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult { Box::pin(async move { for (height, block) in self.blocks.iter().enumerate() { if block.header.block_hash() == *header_hash { @@ -254,8 +260,7 @@ impl BlockSource for Blockchain { return Err(BlockSourceError::persistent("block not found")); } } - - return Ok(block.clone()); + return Ok(BlockData::FullBlock(block.clone())); } } Err(BlockSourceError::transient("block not found")) @@ -289,20 +294,20 @@ pub(crate) fn get_random_tx() -> Transaction { let prev_txid_bytes = get_random_bytes(32); Transaction { - version: 2, - lock_time: 0, + version: bitcoin::transaction::Version(2), + lock_time: bitcoin::locktime::absolute::LockTime::from_height(0).unwrap(), input: vec![TxIn { previous_output: OutPoint::new( Txid::from_slice(&prev_txid_bytes).unwrap(), rng.gen_range(0..200), ), - script_sig: Script::new(), + script_sig: ScriptBuf::new(), witness: Witness::new(), - sequence: 0, + sequence: bitcoin::Sequence(0), }], output: vec![TxOut { script_pubkey: Builder::new().push_int(1).into_script(), - value: rng.gen_range(0..21000000000), + value: Amount::from_sat(rng.gen_range(0..21_000_000_000)), }], } } @@ -367,6 +372,17 @@ pub(crate) async fn get_last_n_blocks(chain: &mut Blockchain, n: usize) -> Vec Vec { + last_n_blocks.iter().map(get_full_block).collect() +} + +pub(crate) fn get_full_block(block: &ValidatedBlock) -> Block { + match block.deref() { + BlockData::FullBlock(b) => b.clone(), + _ => panic!("Expected FullBlock"), + } +} + pub(crate) enum MockedServerQuery { Regular, InMempoool, @@ -405,7 +421,7 @@ pub(crate) async fn create_responder( let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let carrier = Carrier::new(bitcoin_cli, bitcoind_reachable, height); - Responder::new(&last_n_blocks, height, carrier, gatekeeper, dbm) + Responder::new(last_n_blocks.as_slice(), height, carrier, gatekeeper, dbm) } pub(crate) async fn create_watcher( @@ -424,7 +440,7 @@ pub(crate) async fn create_watcher( Watcher::new( gatekeeper, responder, - &last_n_blocks, + last_n_blocks.as_slice(), chain.get_block_count(), tower_sk, tower_id, diff --git a/teos/src/tx_index.rs b/teos/src/tx_index.rs index 404c047..c0b4ee4 100644 --- a/teos/src/tx_index.rs +++ b/teos/src/tx_index.rs @@ -1,9 +1,11 @@ use std::collections::{HashMap, VecDeque}; use std::fmt; use std::hash::Hash; +use std::ops::Deref; +use bitcoin::block::Header; use bitcoin::hash_types::BlockHash; -use bitcoin::{BlockHeader, Transaction, Txid}; +use bitcoin::{Transaction, Txid}; use lightning_block_sync::poll::ValidatedBlock; use teos_common::appointment::Locator; @@ -110,29 +112,38 @@ where }; for block in last_n_blocks.iter().rev() { - if let Some(prev_block_hash) = tx_index.blocks.back() { - if block.header.prev_blockhash != *prev_block_hash { - panic!("last_n_blocks contains unchained blocks"); + match block.deref() { + lightning_block_sync::BlockData::HeaderOnly(_) => { + panic!("Expected FullBlock") } - }; + lightning_block_sync::BlockData::FullBlock(block) => { + if let Some(prev_block_hash) = tx_index.blocks.back() { + if block.header.prev_blockhash != *prev_block_hash { + panic!("last_n_blocks contains unchained blocks"); + } + }; - let map = block - .txdata - .iter() - .map(|tx| { - ( - K::from_txid(tx.txid()), - match V::get_type() { - Type::Transaction => V::from_data(Data::Transaction(tx.clone())), - Type::BlockHash => { - V::from_data(Data::BlockHash(block.header.block_hash())) - } - }, - ) - }) - .collect(); + let map = block + .txdata + .iter() + .map(|tx| { + ( + K::from_txid(tx.compute_txid()), + match V::get_type() { + Type::Transaction => { + V::from_data(Data::Transaction(tx.clone())) + } + Type::BlockHash => { + V::from_data(Data::BlockHash(block.header.block_hash())) + } + }, + ) + }) + .collect(); - tx_index.update(block.header, &map); + tx_index.update(block.header, &map); + } + } } tx_index @@ -155,7 +166,7 @@ where } /// Updates the index by adding data from a new block. Removes the oldest block if the index is full afterwards. - pub fn update(&mut self, block_header: BlockHeader, data: &HashMap) { + pub fn update(&mut self, block_header: Header, data: &HashMap) { self.blocks.push_back(block_header.block_hash()); let ks = data @@ -218,8 +229,9 @@ mod tests { use super::*; use std::ops::Deref; - use crate::test_utils::{get_last_n_blocks, Blockchain}; + use crate::test_utils::{get_full_block, get_full_blocks, get_last_n_blocks, Blockchain}; + use bitcoin::hashes::serde_macros::serde_details::SerdeHash; use bitcoin::Block; impl TxIndex @@ -246,10 +258,7 @@ mod tests { let height = 10; let mut chain = Blockchain::default().with_height(height as usize); let last_six_blocks = get_last_n_blocks(&mut chain, 6).await; - let blocks: Vec = last_six_blocks - .iter() - .map(|block| block.deref().clone()) - .collect(); + let blocks: Vec = get_full_blocks(&last_six_blocks); let cache: TxIndex = TxIndex::new(&last_six_blocks, height); assert_eq!(blocks.len(), cache.size); @@ -258,7 +267,7 @@ mod tests { let mut locators = Vec::new(); for tx in block.txdata.iter() { - let locator = Locator::new(tx.txid()); + let locator = Locator::new(tx.compute_txid()); assert!(cache.contains_key(&locator)); locators.push(locator); } @@ -275,19 +284,22 @@ mod tests { let last_n_blocks = get_last_n_blocks(&mut chain, cache_size).await; // last_n_blocks is ordered from latest to earliest - let first_block = last_n_blocks.get(cache_size - 1).unwrap(); - let last_block = last_n_blocks.first().unwrap(); - let mid = last_n_blocks.get(cache_size / 2).unwrap(); + let first_block = get_full_block(last_n_blocks.get(cache_size - 1).unwrap()); + let last_block = get_full_block(last_n_blocks.first().unwrap()); + let mid_block = get_full_block(last_n_blocks.get(cache_size / 2).unwrap()); let cache: TxIndex = TxIndex::new(&last_n_blocks, height as u32); assert_eq!( - cache.get_height(&first_block.block_hash()).unwrap(), + cache.get_height(&first_block.header.block_hash()).unwrap(), height - cache_size + 1 ); - assert_eq!(cache.get_height(&last_block.block_hash()).unwrap(), height); assert_eq!( - cache.get_height(&mid.block_hash()).unwrap(), + cache.get_height(&last_block.header.block_hash()).unwrap(), + height + ); + assert_eq!( + cache.get_height(&mid_block.header.block_hash()).unwrap(), height - cache_size / 2 ); } @@ -302,8 +314,8 @@ mod tests { height as u32, ); - let fake_hash = BlockHash::default(); - assert!(cache.get_height(&fake_hash).is_none()); + let fake_hash = &BlockHash::from_slice_delegated(&[0; 32]).unwrap(); + assert!(cache.get_height(fake_hash).is_none()); } #[tokio::test] @@ -315,36 +327,42 @@ mod tests { // Store the last block to use it for an update and the first to check eviction // Notice that the list of blocks is ordered from last to first. let last_block = last_n_blocks.remove(0); - let first_block = last_n_blocks.last().unwrap().deref().clone(); + let first_block = last_n_blocks.last().unwrap(); // Init the cache with the 6 block before the last let mut cache = TxIndex::new(&last_n_blocks, height); // Update the cache with the last block - let locator_tx_map = last_block + let full_block = get_full_block(&last_block); + let locator_tx_map = full_block .txdata .iter() - .map(|tx| (Locator::new(tx.txid()), tx.clone())) + .map(|tx| (Locator::new(tx.compute_txid()), tx.clone())) .collect(); - cache.update(last_block.deref().header, &locator_tx_map); + let header = full_block.header; + cache.update(header, &locator_tx_map); // Check that the new data is in the cache - assert!(cache.blocks().contains(&last_block.block_hash())); + assert!(cache.blocks().contains(&header.block_hash())); + for (locator, _) in locator_tx_map.iter() { assert!(cache.contains_key(locator)); } + + let block_hash = full_block.header.block_hash(); assert_eq!( - cache.tx_in_block[&last_block.block_hash()], + cache.tx_in_block[&block_hash], locator_tx_map.keys().cloned().collect::>() ); // Check that the data from the first block has been evicted - assert!(!cache.blocks().contains(&first_block.block_hash())); - for tx in first_block.txdata.iter() { - assert!(!cache.contains_key(&Locator::new(tx.txid()))); - } - assert!(!cache.tx_in_block.contains_key(&first_block.block_hash())); + let first_full_block = get_full_block(first_block); + let tx = first_full_block.txdata[0].clone(); + assert!(!cache.contains_key(&Locator::new(tx.compute_txid()))); + + let block_hash = first_full_block.header.block_hash(); + assert!(!cache.tx_in_block.contains_key(&block_hash)); } #[tokio::test] diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 037319d..caf7766 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -4,8 +4,9 @@ use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; +use bitcoin::block::Header; use bitcoin::secp256k1::SecretKey; -use bitcoin::{BlockHeader, Transaction}; +use bitcoin::Transaction; use lightning::chain; use lightning_block_sync::poll::ValidatedBlock; @@ -261,7 +262,7 @@ impl Watcher { "Trigger for locator {} found in cache", appointment.locator() ); - match cryptography::decrypt(appointment.encrypted_blob(), &dispute_tx.txid()) { + match cryptography::decrypt(appointment.encrypted_blob(), &dispute_tx.compute_txid()) { Ok(penalty_tx) => { // Data needs to be added the database straightaway since appointments are // FKs to trackers. If handle breach fails, data will be deleted later. @@ -383,7 +384,10 @@ impl Watcher { let uuids = self.dbm.lock().unwrap().load_uuids(locator); for uuid in uuids { let appointment = self.dbm.lock().unwrap().load_appointment(uuid).unwrap(); - match cryptography::decrypt(appointment.encrypted_blob(), &dispute_tx.txid()) { + match cryptography::decrypt( + appointment.encrypted_blob(), + &dispute_tx.compute_txid(), + ) { Ok(penalty_tx) => { if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( uuid, @@ -493,7 +497,7 @@ impl chain::Listen for Watcher { /// told by the [Gatekeeper]. fn filtered_block_connected( &self, - header: &BlockHeader, + header: &Header, txdata: &chain::transaction::TransactionData, height: u32, ) { @@ -501,7 +505,7 @@ impl chain::Listen for Watcher { let locator_tx_map = txdata .iter() - .map(|(_, tx)| (Locator::new(tx.txid()), (*tx).clone())) + .map(|(_, tx)| (Locator::new(tx.compute_txid()), (*tx).clone())) .collect(); self.locator_cache @@ -522,7 +526,7 @@ impl chain::Listen for Watcher { /// Handle reorgs in the [Watcher]. /// /// Fixes the [LocatorCache] by removing the disconnected data and updates the last_known_block_height. - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + fn block_disconnected(&self, header: &Header, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); self.locator_cache .lock() @@ -640,7 +644,7 @@ mod tests { // (as if simulating a bootstrap from existing data), the data should be properly loaded. for _ in 0..10 { let appointment = generate_dummy_appointment(None).inner; - let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk); watcher .add_appointment(appointment.clone(), user_sig.clone()) .unwrap(); @@ -702,7 +706,7 @@ mod tests { let user_id = UserId(user_pk); watcher.register(user_id).unwrap(); let appointment = generate_dummy_appointment(None).inner; - let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk); // Add the appointment for a new user (twice so we can check that updates work) for _ in 0..2 { @@ -718,7 +722,7 @@ mod tests { let user2_id = UserId(user2_pk); watcher.register(user2_id).unwrap(); - let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk).unwrap(); + let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk); let (receipt, slots, expiry) = watcher .add_appointment(appointment.clone(), user2_sig.clone()) .unwrap(); @@ -732,9 +736,8 @@ mod tests { // If an appointment is already in the Responder, it should bounce let dispute_tx = get_random_tx(); let (uuid, triggered_appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); - let signature = - cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk).unwrap(); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); + let signature = cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk); let (receipt, slots, expiry) = watcher .add_appointment(triggered_appointment.inner.clone(), signature.clone()) .unwrap(); @@ -762,8 +765,8 @@ mod tests { // If the trigger is already in the cache, the appointment will go straight to the Responder let dispute_tx = tip_txs.last().unwrap(); let (uuid, appointment_in_cache) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); - let user_sig = cryptography::sign(&appointment_in_cache.inner.to_vec(), &user_sk).unwrap(); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); + let user_sig = cryptography::sign(&appointment_in_cache.inner.to_vec(), &user_sk); let (receipt, slots, expiry) = watcher .add_appointment(appointment_in_cache.inner, user_sig.clone()) .unwrap(); @@ -779,9 +782,9 @@ mod tests { // Wrong penalty let dispute_tx = &tip_txs[tip_txs.len() - 2]; let (uuid, mut invalid_appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); invalid_appointment.inner.encrypted_blob.reverse(); - let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); + let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk); let (receipt, slots, expiry) = watcher .add_appointment(invalid_appointment.inner, user_sig.clone()) .unwrap(); @@ -803,8 +806,8 @@ mod tests { let dispute_tx = &tip_txs[tip_txs.len() - 2]; let (uuid, invalid_appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); - let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); + let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk); let (receipt, slots, expiry) = watcher .add_appointment(invalid_appointment.inner, user_sig.clone()) .unwrap(); @@ -841,7 +844,7 @@ mod tests { .available_slots = 0; let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - let signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk); assert!(matches!( watcher.add_appointment(appointment.inner, signature), @@ -856,7 +859,7 @@ mod tests { .add_outdated_user(user2_id, START_HEIGHT as u32); let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, None); - let signature = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); + let signature = cryptography::sign(&appointment.inner.to_vec(), &user2_sk); assert!(matches!( watcher.add_appointment(appointment.inner, signature), @@ -875,7 +878,7 @@ mod tests { let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); watcher.register(user_id).unwrap(); - let dispute_txid = get_random_tx().txid(); + let dispute_txid = get_random_tx().compute_txid(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); @@ -917,7 +920,7 @@ mod tests { let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); // Valid triggered appointments should be accepted by the Responder assert_eq!( @@ -937,7 +940,7 @@ mod tests { *watcher.responder.get_carrier().lock().unwrap() = carrier; let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.compute_txid())); assert_eq!( watcher.store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx), TriggeredAppointment::Rejected, @@ -965,7 +968,7 @@ mod tests { let (watcher, _s) = init_watcher(&mut chain).await; let dispute_tx = get_random_tx(); - let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; + let appointment = generate_dummy_appointment(Some(&dispute_tx.compute_txid())).inner; // If the user cannot be properly identified, the request will fail. This can be simulated by providing a wrong signature let wrong_sig = String::from_utf8((0..65).collect()).unwrap(); @@ -981,12 +984,12 @@ mod tests { watcher .add_appointment( appointment.clone(), - cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &user_sk), ) .unwrap(); let message = format!("get appointment {}", appointment.locator); - let signature = cryptography::sign(message.as_bytes(), &user_sk).unwrap(); + let signature = cryptography::sign(message.as_bytes(), &user_sk); let info = watcher .get_appointment(appointment.locator, &signature) .unwrap(); @@ -1012,7 +1015,7 @@ mod tests { let tracker = TransactionTracker::new(breach, user_id, status); let tracker_message = format!("get appointment {}", appointment.locator); - let tracker_signature = cryptography::sign(tracker_message.as_bytes(), &user_sk).unwrap(); + let tracker_signature = cryptography::sign(tracker_message.as_bytes(), &user_sk); let info = watcher .get_appointment(appointment.locator, &tracker_signature) .unwrap(); @@ -1030,7 +1033,7 @@ mod tests { let user2_id = UserId(user2_pk); watcher.register(user2_id).unwrap(); - let signature2 = cryptography::sign(message.as_bytes(), &user2_sk).unwrap(); + let signature2 = cryptography::sign(message.as_bytes(), &user2_sk); assert!(matches!( watcher.get_appointment(appointment.locator, &signature2), Err(GetAppointmentFailure::NotFound { .. }) @@ -1055,7 +1058,7 @@ mod tests { // Let's create some locators based on the transactions in the last block let locator_tx_map: HashMap<_, _> = (0..10) .map(|_| get_random_tx()) - .map(|tx| (Locator::new(tx.txid()), tx)) + .map(|tx| (Locator::new(tx.compute_txid()), tx)) .collect(); let (user_sk, user_pk) = get_random_keypair(); @@ -1067,8 +1070,8 @@ mod tests { for (i, (l, tx)) in locator_tx_map.iter().enumerate() { // Track some of the these transactions. if i % 2 == 0 { - let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let appointment = generate_dummy_appointment(Some(&tx.compute_txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); watcher.add_appointment(appointment, signature).unwrap(); breaches.insert(*l, tx.clone()); } @@ -1086,7 +1089,7 @@ mod tests { // Let's create some locators based on the transactions in the last block let breaches: HashMap<_, _> = (0..10) .map(|_| get_random_tx()) - .map(|tx| (Locator::new(tx.txid()), tx)) + .map(|tx| (Locator::new(tx.compute_txid()), tx)) .collect(); let (user_sk, user_pk) = get_random_keypair(); @@ -1095,8 +1098,8 @@ mod tests { // Let the watcher track these breaches. for (_, tx) in breaches.iter() { - let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let appointment = generate_dummy_appointment(Some(&tx.compute_txid())).inner; + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); watcher.add_appointment(appointment, signature).unwrap(); } @@ -1111,7 +1114,7 @@ mod tests { // Let's create some locators based on the transactions in the last block let breaches: HashMap<_, _> = (0..10) .map(|_| get_random_tx()) - .map(|tx| (Locator::new(tx.txid()), tx)) + .map(|tx| (Locator::new(tx.compute_txid()), tx)) .collect(); let (user_sk, user_pk) = get_random_keypair(); @@ -1122,14 +1125,14 @@ mod tests { // Let the watcher track these breaches. for (i, (_, tx)) in breaches.iter().enumerate() { let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&tx.compute_txid())); let mut appointment = appointment.inner; if i % 2 == 0 { // Mal-format some appointments appointment.encrypted_blob.reverse(); rejected.insert(uuid); }; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); watcher.add_appointment(appointment, signature).unwrap(); } @@ -1154,7 +1157,7 @@ mod tests { // Let's create some locators based on the transactions in the last block let breaches: HashMap<_, _> = (0..10) .map(|_| get_random_tx()) - .map(|tx| (Locator::new(tx.txid()), tx)) + .map(|tx| (Locator::new(tx.compute_txid()), tx)) .collect(); let (user_sk, user_pk) = get_random_keypair(); @@ -1165,9 +1168,9 @@ mod tests { // Let the watcher track these breaches. for tx in breaches.values() { let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&tx.compute_txid())); let appointment = appointment.inner; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); watcher.add_appointment(appointment, signature).unwrap(); uuids.insert(uuid); } @@ -1186,7 +1189,7 @@ mod tests { // Let's create some locators based on the transactions in the last block let breaches: HashMap<_, _> = (0..10) .map(|_| get_random_tx()) - .map(|tx| (Locator::new(tx.txid()), tx)) + .map(|tx| (Locator::new(tx.compute_txid()), tx)) .collect(); let (user_sk, user_pk) = get_random_keypair(); @@ -1197,14 +1200,14 @@ mod tests { // Let the watcher track these breaches. for (i, (_, tx)) in breaches.iter().enumerate() { let (uuid, appointment) = - generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); + generate_dummy_appointment_with_user(user_id, Some(&tx.compute_txid())); let mut appointment = appointment.inner; if i % 2 == 0 { // Mal-format some appointments, they should be returned as rejected. appointment.encrypted_blob.reverse(); rejected_breaches.insert(uuid); }; - let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let signature = cryptography::sign(&appointment.to_vec(), &user_sk); watcher.add_appointment(appointment, signature).unwrap(); } @@ -1255,11 +1258,11 @@ mod tests { let uuid1 = UUID::new(appointment.locator, user_id); let uuid2 = UUID::new(appointment.locator, user2_id); - let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); + let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk); watcher .add_appointment(appointment.clone(), user_sig) .unwrap(); - let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk).unwrap(); + let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk); watcher.add_appointment(appointment, user2_sig).unwrap(); // Outdate the first user's registration. @@ -1298,8 +1301,8 @@ mod tests { // Check triggers. Add a new appointment and trigger it with valid data. let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); - let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); + let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk); watcher.add_appointment(appointment.inner, sig).unwrap(); assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); @@ -1316,10 +1319,10 @@ mod tests { // Checks invalid triggers. Add a new appointment and trigger it with invalid data. let dispute_tx = get_random_tx(); let (uuid, mut appointment) = - generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); // Modify the encrypted blob so the data is invalid. appointment.inner.encrypted_blob.reverse(); - let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); + let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk); watcher.add_appointment(appointment.inner, sig).unwrap(); let block = chain.generate(Some(vec![dispute_tx])); @@ -1335,8 +1338,8 @@ mod tests { // Check triggering with a valid formatted transaction but that is rejected by the Responder. let dispute_tx = get_random_tx(); let (uuid, appointment) = - generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); - let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); + generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.compute_txid())); + let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk); watcher.add_appointment(appointment.inner, sig).unwrap(); // Set the carrier response diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index ed9aa64..d216835 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -25,12 +25,12 @@ tonic = { version = "0.11", features = [ "tls", "transport" ] } tokio = { version = "1.5", features = [ "rt-multi-thread", "fs" ] } # Bitcoin and Lightning -bitcoin = "0.28.0" -cln-plugin = "0.1.2" +bitcoin = "0.32.0" +cln-plugin = "0.3.0" # Local teos-common = { path = "../teos-common" } [dev-dependencies] mockito = "0.32.4" -tempdir = "0.3.7" \ No newline at end of file +tempdir = "0.3.7" diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f7942cc..74ebaa1 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -8,7 +8,8 @@ use serde_json::json; use tokio::io::{stdin, stdout}; use tokio::sync::mpsc::unbounded_channel; -use cln_plugin::options::{ConfigOption, Value}; +use cln_plugin::options::config_type::DefaultInteger; +use cln_plugin::options::ConfigOption; use cln_plugin::{anyhow, Builder, Error, Plugin}; use teos_common::appointment::{Appointment, Locator}; @@ -28,6 +29,31 @@ use watchtower_plugin::retrier::RetryManager; use watchtower_plugin::wt_client::{RevocationData, WTClient}; use watchtower_plugin::{constants, TowerStatus}; +const DEV_WT_MAX_RETRY_INTERVAL_CONFIG: ConfigOption = + ConfigOption::new_i64_with_default( + constants::DEV_WT_MAX_RETRY_INTERVAL, + constants::DEFAULT_DEV_WT_MAX_RETRY_INTERVAL, + constants::DEV_WT_MAX_RETRY_INTERVAL_DESC, + ); + +const WT_AUTO_RETRY_DELAY_CONFIG: ConfigOption = ConfigOption::new_i64_with_default( + constants::WT_AUTO_RETRY_DELAY, + constants::DEFAULT_WT_AUTO_RETRY_DELAY, + constants::WT_AUTO_RETRY_DELAY_DESC, +); + +const WT_MAX_RETRY_TIME_CONFIG: ConfigOption = ConfigOption::new_i64_with_default( + constants::WT_MAX_RETRY_TIME, + constants::DEFAULT_WT_MAX_RETRY_TIME, + constants::WT_MAX_RETRY_TIME_DESC, +); + +const WT_PORT_CONFG: ConfigOption = ConfigOption::new_i64_with_default( + constants::WT_PORT, + constants::DEFAULT_WT_PORT, + constants::WT_PORT_DESC, +); + fn to_cln_error(e: RequestError) -> Error { let e = match e { RequestError::ConnectionError(e) => anyhow!(e), @@ -76,7 +102,7 @@ async fn register( // which is not available in the current version of `cln-plugin` (but already on master). Add it for the next release. let port = params.port.unwrap_or( - u16::try_from(plugin.option(constants::WT_PORT).unwrap().as_i64().unwrap()) + u16::try_from(plugin.option(&WT_PORT_CONFG).unwrap()) .map_err(|_| anyhow!("{} out of range", constants::WT_PORT))?, ); @@ -162,7 +188,7 @@ async fn get_subscription_info( } }?; - let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk).unwrap(); + let signature = cryptography::sign("get subscription info".as_bytes(), &user_sk); let response: common_msgs::GetSubscriptionInfoResponse = process_post_response( post_request( @@ -207,8 +233,7 @@ async fn get_appointment( let signature = cryptography::sign( format!("get appointment {}", params.locator).as_bytes(), &user_sk, - ) - .unwrap(); + ); let response: ApiResponse = process_post_response( post_request( @@ -422,8 +447,7 @@ async fn on_commitment_revocation( let signature = cryptography::sign( &appointment.to_vec(), &plugin.state().lock().unwrap().user_sk, - ) - .unwrap(); + ); // Looks like we cannot iterate through towers given a locked state is not Send (due to the async call), // so we need to clone the bare minimum. @@ -534,26 +558,10 @@ async fn main() -> Result<(), Error> { }; let builder = Builder::new(stdin(), stdout()) - .option(ConfigOption::new( - constants::WT_PORT, - Value::Integer(constants::DEFAULT_WT_PORT), - constants::WT_PORT_DESC, - )) - .option(ConfigOption::new( - constants::WT_MAX_RETRY_TIME, - Value::Integer(constants::DEFAULT_WT_MAX_RETRY_TIME), - constants::WT_MAX_RETRY_TIME_DESC, - )) - .option(ConfigOption::new( - constants::WT_AUTO_RETRY_DELAY, - Value::Integer(constants::DEFAULT_WT_AUTO_RETRY_DELAY), - constants::WT_AUTO_RETRY_DELAY_DESC, - )) - .option(ConfigOption::new( - constants::DEV_WT_MAX_RETRY_INTERVAL, - Value::Integer(constants::DEFAULT_DEV_WT_MAX_RETRY_INTERVAL), - constants::DEV_WT_MAX_RETRY_INTERVAL_DESC, - )) + .option(WT_PORT_CONFG) + .option(WT_MAX_RETRY_TIME_CONFIG) + .option(WT_AUTO_RETRY_DELAY_CONFIG) + .option(DEV_WT_MAX_RETRY_INTERVAL_CONFIG) .rpcmethod( constants::RPC_REGISTER_TOWER, constants::RPC_REGISTER_TOWER_DESC, @@ -629,34 +637,18 @@ async fn main() -> Result<(), Error> { .await, )); - let max_elapsed_time = u16::try_from( - midstate - .option(constants::WT_MAX_RETRY_TIME) - .unwrap() - .as_i64() - .unwrap(), - ) - .inspect_err(|_| { - log::error!("{} out of range", constants::WT_MAX_RETRY_TIME); - })?; + let max_elapsed_time = u16::try_from(midstate.option(&WT_MAX_RETRY_TIME_CONFIG).unwrap()) + .inspect_err(|_| { + log::error!("{} out of range", constants::WT_MAX_RETRY_TIME); + })?; - let auto_retry_delay = u32::try_from( - midstate - .option(constants::WT_AUTO_RETRY_DELAY) - .unwrap() - .as_i64() - .unwrap(), - ) - .inspect_err(|_| { - log::error!("{} out of range", constants::WT_AUTO_RETRY_DELAY); - })?; + let auto_retry_delay = u32::try_from(midstate.option(&WT_AUTO_RETRY_DELAY_CONFIG).unwrap()) + .inspect_err(|_| { + log::error!("{} out of range", constants::WT_AUTO_RETRY_DELAY); + })?; let max_interval_time = u16::try_from( - midstate - .option(constants::DEV_WT_MAX_RETRY_INTERVAL) - .unwrap() - .as_i64() - .unwrap(), + midstate.option(&DEV_WT_MAX_RETRY_INTERVAL_CONFIG).unwrap(), ) .inspect_err(|_| { log::error!("{} out of range", constants::DEV_WT_MAX_RETRY_INTERVAL); diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index dcbfaa6..e89d4e5 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -491,7 +491,7 @@ impl Retrier { &net_addr, &proxy, &appointment, - &cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), + &cryptography::sign(&appointment.to_vec(), &user_sk), ) .await { @@ -652,7 +652,7 @@ mod tests { // Prepare the mock response let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); add_appointment_receipt.sign(&tower_sk); @@ -793,7 +793,7 @@ mod tests { // Prepare the mock response let mut server = mockito::Server::new_async().await; let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); add_appointment_receipt.sign(&tower_sk); @@ -974,7 +974,7 @@ mod tests { // Prepare the mock response let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); // Sign with a random key so it counts as misbehaving @@ -1113,7 +1113,7 @@ mod tests { re_registration_receipt.sign(&tower_sk); let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); add_appointment_receipt.sign(&tower_sk); @@ -1239,9 +1239,15 @@ mod tests { MAX_ELAPSED_TIME as f64 + MAX_RUN_TIME, )) .await; - let state = wt_client.lock().unwrap(); - assert!(state.get_retrier_status(&tower_id).unwrap().is_idle()); + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .unwrap() + .is_idle()); + + let state = wt_client.lock().unwrap(); let tower = state.towers.get(&tower_id).unwrap(); assert!(tower.pending_appointments.contains(&appointment.locator)); assert_eq!(tower.status, TowerStatus::Unreachable); @@ -1267,11 +1273,11 @@ mod tests { // Create the receipts, the responses and set the mocks let mut appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); let mut appointment2_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment2.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment2.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); appointment_receipt.sign(&tower_sk); @@ -1368,7 +1374,7 @@ mod tests { // Prepare the mock response let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); add_appointment_receipt.sign(&tower_sk); @@ -1439,7 +1445,7 @@ mod tests { // Prepare the mock response let mut add_appointment_receipt = AppointmentReceipt::new( - cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk), 42, ); add_appointment_receipt.sign(&cryptography::get_random_keypair().0); From 469e247ac3d3f16a89a73785ce8b6d2bdf0d586b Mon Sep 17 00:00:00 2001 From: oyindamola oladapo Date: Sat, 12 Jul 2025 22:21:51 +0100 Subject: [PATCH 116/119] ci: fix and simplify cln-plugin job - Correct YAML indentation - Fix Poetry installation (no --user, avoids not found errors) - Remove unnecessary steps and sudo usage - Modularize CLN cache jobs - Run `poetry run make` concurrently to speed up build --- .github/workflows/cln-plugin.yaml | 39 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 6b710ab..9451239 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -11,8 +11,22 @@ env: cln_version: "24.11.1" jobs: + check-cln-cache: + runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.cache.outputs.cache-hit }} + steps: + - name: Check CLN cache + id: cache + uses: actions/cache@v4 + with: + path: lightning + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} + cache-cln: runs-on: ubuntu-latest + needs: check-cln-cache + if: ${{ needs.check-cln-cache.outputs.cache-hit != 'true' }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -23,26 +37,22 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Create CLN cache - id: cache-cln uses: actions/cache@v4 - env: - cache-name: cache-cln-dev with: path: lightning - key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} - name: Compile CLN env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring - if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }} run: | sudo apt-get update && sudo apt-get install -y gettext git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }} pip install --user poetry && poetry install - cargo update -p time - ./configure && poetry run make + ./configure && poetry run make -j 8 cln-plugin: needs: cache-cln + if: ${{ always() }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -56,7 +66,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: stable components: rustfmt, clippy - name: Install bitcoind run: | @@ -66,15 +76,12 @@ jobs: - name: Load CLN cache id: cache-cln uses: actions/cache@v4 - env: - cache-name: cache-cln-dev with: path: lightning - key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} - - name: Link CLN - run: | - source $HOME/.cargo/env - cd lightning && sudo make install + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} + - name: Link CLN + run: | + cd lightning && sudo PATH=$PATH make install - name: Install teos and the plugin run: | cargo install --locked --path teos @@ -82,7 +89,7 @@ jobs: - name: Add test dependencies run: | cd watchtower-plugin/tests - pip install --user poetry && poetry install + pip install --user poetry && poetry install --no-root - name: Run tests run: | cd watchtower-plugin/tests From 3fd4f413252fbd343cee32f42c0197176fbf06a4 Mon Sep 17 00:00:00 2001 From: Joseph Goulden Date: Sat, 27 Sep 2025 15:29:37 +0100 Subject: [PATCH 117/119] Allow watchtower client to call https endoints --- watchtower-plugin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index 74ebaa1..a344084 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -107,7 +107,7 @@ async fn register( ); let tower_net_addr = { - if !host.starts_with("http://") { + if !host.starts_with("http://") && !host.starts_with("https://") { host = format!("http://{host}") } NetAddr::new(format!("{host}:{port}")) From 4824cb3372add1efd98ed13cb39515a286a2308a Mon Sep 17 00:00:00 2001 From: oyindamola oladapo Date: Sat, 12 Jul 2025 22:21:51 +0100 Subject: [PATCH 118/119] ci: fix and simplify cln-plugin job - Correct YAML indentation - Fix Poetry installation (no --user, avoids not found errors) - Remove unnecessary steps and sudo usage - Modularize CLN cache jobs - Run `poetry run make` concurrently to speed up build --- .github/workflows/cln-plugin.yaml | 39 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cln-plugin.yaml b/.github/workflows/cln-plugin.yaml index 6b710ab..9451239 100644 --- a/.github/workflows/cln-plugin.yaml +++ b/.github/workflows/cln-plugin.yaml @@ -11,8 +11,22 @@ env: cln_version: "24.11.1" jobs: + check-cln-cache: + runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.cache.outputs.cache-hit }} + steps: + - name: Check CLN cache + id: cache + uses: actions/cache@v4 + with: + path: lightning + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} + cache-cln: runs-on: ubuntu-latest + needs: check-cln-cache + if: ${{ needs.check-cln-cache.outputs.cache-hit != 'true' }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -23,26 +37,22 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Create CLN cache - id: cache-cln uses: actions/cache@v4 - env: - cache-name: cache-cln-dev with: path: lightning - key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} - name: Compile CLN env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring - if: ${{ steps.cache-cln.outputs.cache-hit != 'true' }} run: | sudo apt-get update && sudo apt-get install -y gettext git clone https://github.com/ElementsProject/lightning.git && cd lightning && git checkout v${{ env.cln_version }} pip install --user poetry && poetry install - cargo update -p time - ./configure && poetry run make + ./configure && poetry run make -j 8 cln-plugin: needs: cache-cln + if: ${{ always() }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -56,7 +66,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: stable components: rustfmt, clippy - name: Install bitcoind run: | @@ -66,15 +76,12 @@ jobs: - name: Load CLN cache id: cache-cln uses: actions/cache@v4 - env: - cache-name: cache-cln-dev with: path: lightning - key: ${{ runner.os }}-build-${{ env.cache-name }}-v${{ env.cln_version }} - - name: Link CLN - run: | - source $HOME/.cargo/env - cd lightning && sudo make install + key: ${{ runner.os }}-build-cache-cln-dev-v${{ env.cln_version }} + - name: Link CLN + run: | + cd lightning && sudo PATH=$PATH make install - name: Install teos and the plugin run: | cargo install --locked --path teos @@ -82,7 +89,7 @@ jobs: - name: Add test dependencies run: | cd watchtower-plugin/tests - pip install --user poetry && poetry install + pip install --user poetry && poetry install --no-root - name: Run tests run: | cd watchtower-plugin/tests From 70ea8b5625e5c327e35e1b0e9e65a890a1874a5d Mon Sep 17 00:00:00 2001 From: Joseph Goulden Date: Wed, 8 Oct 2025 22:12:40 +0100 Subject: [PATCH 119/119] Add nix flake to build teos with nixpkgs 25.05 --- .github/workflows/build.yaml | 15 ++++- .gitignore | 3 +- flake.lock | 116 +++++++++++++++++++++++++++++++++++ flake.nix | 114 ++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 88768c1..e80276c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -66,4 +66,17 @@ jobs: uses: psf/black@stable with: src: "./watchtower-plugin/tests" - options: "--check -l 120" \ No newline at end of file + options: "--check -l 120" + + check-flake: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Check Nix flake inputs + uses: DeterminateSystems/flake-checker-action@v12 + - name: Install Nix + uses: cachix/install-nix-action@v31 + - name: Check flake + run: nix flake check + diff --git a/.gitignore b/.gitignore index 3b7e0f7..599e08c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target __pycache__ .vscode -.idea \ No newline at end of file +.idea +result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ca135b2 --- /dev/null +++ b/flake.lock @@ -0,0 +1,116 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1758758545, + "narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=", + "owner": "ipetkov", + "repo": "crane", + "rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1758782550, + "narHash": "sha256-olCvyP5r6+HQTl2EUudtjlA5UammsBpkzAl0l9+utZc=", + "owner": "nix-community", + "repo": "fenix", + "rev": "32f4e350c03cc5762be811e9c700e8696cd13c02", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1758589230, + "narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1758620797, + "narHash": "sha256-Ly4rHgrixFMBnkbMursVt74mxnntnE6yVdF5QellJ+A=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "905641f3520230ad6ef421bcf5da9c6b49f2479b", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a272a74 --- /dev/null +++ b/flake.nix @@ -0,0 +1,114 @@ +{ + description = "Build teos (The Eye of Satoshi) server and plugin"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + + crane.url = "github:ipetkov/crane"; + + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + nixpkgs, + crane, + fenix, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + inherit (pkgs) lib; + + craneLib = (crane.mkLib pkgs).overrideToolchain fenix.packages.${system}.stable.minimalToolchain; + + env = { + PROTOC = "${pkgs.protobuf}/bin/protoc"; + PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.openssl.out ]; + }; + commonArgs = { + inherit env; + strictDeps = true; + + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.rustfmt # needed for tonic build + pkgs.cacert + pkgs.openssl.dev + ]; + + buildInputs = + [ ] + ++ lib.optionals pkgs.stdenv.isDarwin [ + # Additional darwin specific inputs can be set here + pkgs.libiconv + ]; + }; + + fileSetForCrate = + crate: + lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.unions [ + ./Cargo.toml + ./Cargo.lock + ./teos-common + ./teos + ./watchtower-plugin + crate + ]; + }; + + plugin = craneLib.buildPackage ( + commonArgs + // { + pname = "watchtower-plugin"; + cargoExtraArgs = "-p watchtower-plugin"; + src = fileSetForCrate ./watchtower-plugin; + inherit (craneLib.crateNameFromCargoToml { cargoToml = ./watchtower-plugin/Cargo.toml; }) version; + } + ); + teos = craneLib.buildPackage ( + commonArgs + // { + pname = "teos"; + cargoExtraArgs = "-p teos"; + src = fileSetForCrate ./teos; + inherit (craneLib.crateNameFromCargoToml { cargoToml = ./teos/Cargo.toml; }) version; + } + ); + in + { + packages = { + inherit plugin teos; + default = teos; + }; + + apps = { + plugin = flake-utils.lib.mkApp { drv = plugin; }; + teos = flake-utils.lib.mkApp { drv = teos; }; + }; + + formatter = pkgs.nixfmt-tree; + + checks = { + inherit teos plugin; + }; + + devShells.default = craneLib.devShell { + inherit env; + packages = commonArgs.buildInputs ++ commonArgs.nativeBuildInputs; + }; + } + ); +}